-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ep-factory): Implemented Event Factory and User Event Factory (#194
- Loading branch information
1 parent
239b687
commit 6794260
Showing
6 changed files
with
1,245 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
# Copyright 2019 Optimizely | ||
# 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. | ||
|
||
from optimizely.helpers import enums | ||
from optimizely.helpers import event_tag_utils | ||
from optimizely.helpers import validator | ||
from . import log_event | ||
from . import payload | ||
from . import user_event | ||
|
||
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' | ||
|
||
|
||
class EventFactory(object): | ||
""" EventFactory builds LogEvent object from a given UserEvent. | ||
This class serves to separate concerns between events in the SDK and the API used | ||
to record the events via the Optimizely Events API ("https://developers.optimizely.com/x/events/api/index.html") | ||
""" | ||
|
||
EVENT_ENDPOINT = 'https://logx.optimizely.com/v1/events' | ||
HTTP_VERB = 'POST' | ||
HTTP_HEADERS = {'Content-Type': 'application/json'} | ||
ACTIVATE_EVENT_KEY = 'campaign_activated' | ||
|
||
@classmethod | ||
def create_log_event(cls, user_events, logger): | ||
""" Create LogEvent instance. | ||
Args: | ||
user_events: A single UserEvent instance or a list of UserEvent instances. | ||
logger: Provides a logger instance. | ||
Returns: | ||
LogEvent instance. | ||
""" | ||
|
||
if not isinstance(user_events, list): | ||
user_events = [user_events] | ||
|
||
visitors = [] | ||
|
||
for event in user_events: | ||
visitor = cls._create_visitor(event, logger) | ||
|
||
if visitor: | ||
visitors.append(visitor) | ||
|
||
user_context = event.event_context | ||
|
||
event_batch = payload.EventBatch( | ||
user_context.account_id, | ||
user_context.project_id, | ||
user_context.revision, | ||
user_context.client_name, | ||
user_context.client_version, | ||
user_context.anonymize_ip, | ||
True | ||
) | ||
|
||
if len(visitors) == 0: | ||
return None | ||
|
||
event_batch.visitors = visitors | ||
|
||
event_params = event_batch.get_event_params() | ||
|
||
return log_event.LogEvent(cls.EVENT_ENDPOINT, event_params, cls.HTTP_VERB, cls.HTTP_HEADERS) | ||
|
||
@classmethod | ||
def _create_visitor(cls, event, logger): | ||
""" Helper method to create Visitor instance for event_batch. | ||
Args: | ||
event: Instance of UserEvent. | ||
logger: Provides a logger instance. | ||
Returns: | ||
Instance of Visitor. None if: | ||
- event is invalid. | ||
""" | ||
|
||
if isinstance(event, user_event.ImpressionEvent): | ||
decision = payload.Decision( | ||
event.experiment.layerId, | ||
event.experiment.id, | ||
event.variation.id, | ||
) | ||
|
||
snapshot_event = payload.SnapshotEvent( | ||
event.experiment.layerId, | ||
event.uuid, | ||
cls.ACTIVATE_EVENT_KEY, | ||
event.timestamp | ||
) | ||
|
||
snapshot = payload.Snapshot([snapshot_event], [decision]) | ||
|
||
visitor = payload.Visitor([snapshot], event.visitor_attributes, event.user_id) | ||
|
||
return visitor | ||
|
||
elif isinstance(event, user_event.ConversionEvent): | ||
revenue = event_tag_utils.get_revenue_value(event.event_tags) | ||
value = event_tag_utils.get_numeric_value(event.event_tags, logger) | ||
|
||
snapshot_event = payload.SnapshotEvent( | ||
event.event.id, | ||
event.uuid, | ||
event.event.key, | ||
event.timestamp, | ||
revenue, | ||
value, | ||
event.event_tags | ||
) | ||
|
||
snapshot = payload.Snapshot([snapshot_event]) | ||
|
||
visitor = payload.Visitor([snapshot], event.visitor_attributes, event.user_id) | ||
|
||
return visitor | ||
|
||
else: | ||
logger.error('Invalid user event.') | ||
return None | ||
|
||
@staticmethod | ||
def build_attribute_list(attributes, project_config): | ||
""" Create Vistor Attribute List. | ||
Args: | ||
attributes: Dict representing user attributes and values which need to be recorded or None. | ||
project_config: Instance of ProjectConfig. | ||
Returns: | ||
List consisting of valid attributes for the user. Empty otherwise. | ||
""" | ||
|
||
attributes_list = [] | ||
|
||
if project_config is None: | ||
return attributes_list | ||
|
||
if isinstance(attributes, dict): | ||
for attribute_key in attributes.keys(): | ||
attribute_value = attributes.get(attribute_key) | ||
# Omit attribute values that are not supported by the log endpoint. | ||
if validator.is_attribute_valid(attribute_key, attribute_value): | ||
attribute_id = project_config.get_attribute_id(attribute_key) | ||
if attribute_id: | ||
attributes_list.append( | ||
payload.VisitorAttribute( | ||
attribute_id, | ||
attribute_key, | ||
CUSTOM_ATTRIBUTE_FEATURE_TYPE, | ||
attribute_value) | ||
) | ||
|
||
# Append Bot Filtering Attribute | ||
bot_filtering_value = project_config.get_bot_filtering_value() | ||
if isinstance(bot_filtering_value, bool): | ||
attributes_list.append( | ||
payload.VisitorAttribute( | ||
enums.ControlAttributes.BOT_FILTERING, | ||
enums.ControlAttributes.BOT_FILTERING, | ||
CUSTOM_ATTRIBUTE_FEATURE_TYPE, | ||
bot_filtering_value) | ||
) | ||
|
||
return attributes_list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Copyright 2019 Optimizely | ||
# 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. | ||
|
||
|
||
class LogEvent(object): | ||
""" Representation of an event which can be sent to Optimizely events API. """ | ||
|
||
def __init__(self, url, params, http_verb=None, headers=None): | ||
self.url = url | ||
self.params = params | ||
self.http_verb = http_verb or 'POST' | ||
self.headers = headers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Copyright 2019 Optimizely | ||
# 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. | ||
|
||
from . import event_factory | ||
from . import user_event | ||
|
||
|
||
class UserEventFactory(object): | ||
""" UserEventFactory builds impression and conversion events from a given UserEvent. """ | ||
|
||
@classmethod | ||
def create_impression_event(cls, project_config, activated_experiment, variation_id, user_id, user_attributes): | ||
""" Create impression Event to be sent to the logging endpoint. | ||
Args: | ||
project_config: Instance of ProjectConfig. | ||
experiment: Experiment for which impression needs to be recorded. | ||
variation_id: ID for variation which would be presented to user. | ||
user_id: ID for user. | ||
attributes: Dict representing user attributes and values which need to be recorded. | ||
Returns: | ||
Event object encapsulating the impression event. None if: | ||
- activated_experiment is None. | ||
""" | ||
|
||
if not activated_experiment: | ||
return None | ||
|
||
experiment_key = activated_experiment.key | ||
variation = project_config.get_variation_from_id(experiment_key, variation_id) | ||
|
||
event_context = user_event.EventContext( | ||
project_config.account_id, | ||
project_config.project_id, | ||
project_config.revision, | ||
project_config.anonymize_ip | ||
) | ||
|
||
return user_event.ImpressionEvent( | ||
event_context, | ||
user_id, | ||
activated_experiment, | ||
event_factory.EventFactory.build_attribute_list(user_attributes, project_config), | ||
variation, | ||
project_config.get_bot_filtering_value() | ||
) | ||
|
||
@classmethod | ||
def create_conversion_event(cls, project_config, event_key, user_id, user_attributes, event_tags): | ||
""" Create conversion Event to be sent to the logging endpoint. | ||
Args: | ||
project_config: Instance of ProjectConfig. | ||
event_key: Key representing the event which needs to be recorded. | ||
user_id: ID for user. | ||
attributes: Dict representing user attributes and values. | ||
event_tags: Dict representing metadata associated with the event. | ||
Returns: | ||
Event object encapsulating the conversion event. | ||
""" | ||
|
||
event_context = user_event.EventContext( | ||
project_config.account_id, | ||
project_config.project_id, | ||
project_config.revision, | ||
project_config.anonymize_ip | ||
) | ||
|
||
return user_event.ConversionEvent( | ||
event_context, | ||
project_config.get_event(event_key), | ||
user_id, | ||
event_factory.EventFactory.build_attribute_list(user_attributes, project_config), | ||
event_tags, | ||
project_config.get_bot_filtering_value() | ||
) |
Oops, something went wrong.