-
Notifications
You must be signed in to change notification settings - Fork 35
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
feat(ep-factory): Implemented Event Factory and User Event Factory #194
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
232f0a0
feat: Add event_batch datamodels.
2320939
fix: unit test fixes.
fbb5a68
Merge branch 'master' into epmodels
mnoman09 c162465
feat: add event_factory and user_event_factory for event processing.
633b095
Removed third party dependency
msohailhussain 8a5f531
Merge branch 'master' into sohail/pr-189
mnoman09 35fcd82
Merge remote-tracking branch 'origin/sohail/pr-189' into sohail/pr-193
mnoman09 d7201cb
Removed HasAttr
mnoman09 c829fca
Merge branch 'master' into sohail/pr-189
mnoman09 c3c9d46
Addressed feedback. Restructured classes.
msohailhussain a2eeb85
Merge branch 'sohail/pr-189' into sohail/pr-193
mnoman09 a045291
update: structural changes.
23ab6ce
fix: addressed more feedback
6474e98
update: removed print statements.
mnoman09 1184568
merged PR 189
mnoman09 592a306
fix: use VisitorAttribute class to create visitor attributes list.
c4a412a
fix: fix log_event payload.
2a3374c
removed commented code
mnoman09 ce44c11
fix linter error.
mnoman09 d4f2c66
Merge branch 'master' into sohail/pr-189
rashidsp 80b0963
fix: addressed minor feedback.
071460a
fix: addressed minor feedback.
900d96d
Merge branch 'sohail/pr-189' of github.com:optimizely/python-sdk into…
8cd8547
Merge branch 'sohail/pr-189' into sohail/pr-193
e628e0f
fix: addressed review comments.
mariamjamal94 f851754
fix: addresses review comments.
mariamjamal94 65a752b
fix: addresses review comment.
mariamjamal94 8998580
fix: updated docstring for LogEvent.
mariamjamal94 c3d5b07
Merge branch 'master' into sohail/pr-193
mariamjamal94 b5d461b
Merge branch 'master' into sohail/pr-193
aliabbasrizvi 5ef24de
fix: address import order issues.
mariamjamal94 1d5aeca
Merge branch 'master' into sohail/pr-193
oakbani f97d430
nit addressed
msohailhussain File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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): | ||
mjc1283 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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): | ||
mjc1283 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. Last 3 imports are not alphabetic.