Skip to content

Commit

Permalink
Merge pull request #75 from botstory/feature/google-analytics-interface
Browse files Browse the repository at this point in the history
Feature/google analytics interface
  • Loading branch information
hyzhak committed Nov 14, 2016
2 parents 200b578 + 09862d3 commit de0606a
Show file tree
Hide file tree
Showing 26 changed files with 1,110 additions and 159 deletions.
31 changes: 28 additions & 3 deletions botstory/ast/processor.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import asyncio
import logging
import inspect

from .. import matchers
from . import parser, callable, forking
from .. import matchers
from ..integrations import mocktracker

logger = logging.getLogger(__name__)

Expand All @@ -15,6 +15,7 @@ def __init__(self, parser_instance, library, middlewares=[]):
self.middlewares = middlewares
self.parser_instance = parser_instance
self.storage = None
self.tracker = mocktracker.MockTracker()

def add_interface(self, interface):
if self.storage:
Expand All @@ -27,15 +28,34 @@ def add_storage(self, storage):
for interface in self.interfaces:
interface.add_storage(storage)

def add_tracker(self, tracker):
logger.debug('add_tracker')
logger.debug(tracker)
self.tracker = tracker

def clear(self):
self.interfaces = []
self.storage = None

async def match_message(self, message):
"""
because match_message is recursive we split function to
public match_message and private _match_message
:param message:
:return:
"""
logger.debug('')
logger.debug('> match_message <')
logger.debug('')
logger.debug(' {} '.format(message))
self.tracker.new_message(
user=message and message['user'],
data=message['data'],
)
return await self._match_message(message)

async def _match_message(self, message):
session = message['session']
if len(session['stack']) > 0:
logger.debug(' check stack')
Expand Down Expand Up @@ -131,6 +151,11 @@ async def process_story(self, session, message, compiled_story, idx=0, story_arg
story_part = story_line[idx]

logger.debug(' going to call: {}'.format(story_part.__name__))
self.tracker.story(
user=message and message['user'],
story_name=compiled_story.topic,
story_part_name=story_part.__name__,
)

# TODO: just should skip story part
# but it should be done in process_next_part_of_story
Expand Down Expand Up @@ -190,7 +215,7 @@ async def process_story(self, session, message, compiled_story, idx=0, story_arg

if message:
if bubble_up:
waiting_for = await self.match_message(message)
waiting_for = await self._match_message(message)
else:
logger.debug(' we reject bubbling in this call')

Expand Down
32 changes: 32 additions & 0 deletions botstory/ast/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging
from ..integrations.mocktracker import tracker

logger = logging.getLogger(__name__)
_tracker = None


def add_tracker(tracker):
logger.debug('add_tracker')
logger.debug(tracker)
global _tracker
_tracker = tracker


def on_new_user_comes(user):
"""
each interface that create new user should call this method.
as well if someone need to catch new user it is good place
to do it
:param user:
:return:
"""
_tracker.new_user(user)


def clear():
global _tracker
_tracker = tracker.MockTracker()


clear()
15 changes: 15 additions & 0 deletions botstory/integrations/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface.tracker
-----------------

Collects statistics of usage like path through stories.

Implementations
~~~~~~~~~~~~~~~

- `Google Analytics <https://github.com/botstory/botstory/blob/develop/botstory/integrations/ga/tracker.py>`_

*Usage:*
.. code-block:: python
from botstory.integrations.ga import tracker
story.use(tracker.GAStatistics(tracking_id='UA-XXXXX-Y'))
20 changes: 10 additions & 10 deletions botstory/integrations/aiohttp/aiohttp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
import json
import pytest
from . import AioHttpInterface
from ..tests.fake_server import fake_fb
from ..commonhttp import errors
from ..tests import fake_server


@pytest.mark.asyncio
async def test_post(event_loop):
async with fake_fb.Server(event_loop) as server:
async with fake_server.FakeFacebook(event_loop) as server:
async with server.session() as session:
http = AioHttpInterface()
http.session = session

assert await http.post(fake_fb.URI.format('/v2.6/me/messages/'), json={'message': 'hello world!'})
assert await http.post(fake_server.URI.format('/v2.6/me/messages/'), json={'message': 'hello world!'})
assert len(server.history) == 1
req = server.history[-1]['request']
assert req.content_type == 'application/json'
Expand Down Expand Up @@ -82,7 +82,7 @@ async def test_should_not_create_server_if_there_wasnt_any_webhooks():


@pytest.mark.skip(reason='too long')
@pytest.mark.asyncio
@pytest.mark.asynciod
async def test_get_400_from_wrong_domain():
http = AioHttpInterface()
try:
Expand All @@ -102,13 +102,13 @@ async def test_get_400_from_wrong_path():

@pytest.mark.asyncio
async def test_post_to_wrong_path_get_400(event_loop):
async with fake_fb.Server(event_loop) as server:
async with fake_server.FakeFacebook(event_loop) as server:
async with server.session() as session:
http = AioHttpInterface()
http.session = session

try:
await http.post(fake_fb.URI.format('/v2.6/me/messages/mistake'), json={'message': 'hello world!'})
await http.post(fake_server.URI.format('/v2.6/me/messages/mistake'), json={'message': 'hello world!'})
except errors.HttpRequestError as err:
assert err.code == 404

Expand All @@ -134,22 +134,22 @@ async def test_post_400_from_wrong_path():

@pytest.mark.asyncio
async def test_delete_200(event_loop):
async with fake_fb.Server(event_loop) as server:
async with fake_server.FakeFacebook(event_loop) as server:
async with server.session() as session:
http = AioHttpInterface()
http.session = session

await http.delete(fake_fb.URI.format('/v2.6/me/thread_settings'))
await http.delete(fake_server.URI.format('/v2.6/me/thread_settings'))


@pytest.mark.asyncio
async def test_delete_400(event_loop):
async with fake_fb.Server(event_loop) as server:
async with fake_server.FakeFacebook(event_loop) as server:
async with server.session() as session:
http = AioHttpInterface()
http.session = session

try:
await http.delete(fake_fb.URI.format('/v2.6/me/wrong'))
await http.delete(fake_server.URI.format('/v2.6/me/wrong'))
except errors.HttpRequestError as err:
assert err.code == 404
3 changes: 3 additions & 0 deletions botstory/integrations/fb/messenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from . import validate
from .. import commonhttp
from ...middlewares import option
from ...ast import users

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -168,6 +169,8 @@ async def handle(self, data):
gender=messenger_profile_data.get('gender', None),
)

users.on_new_user_comes(user)

session = await self.storage.get_session(facebook_user_id=facebook_user_id)
if not session:
logger.debug(' should create new session for user {}'.format(facebook_user_id))
Expand Down
Empty file.
68 changes: 68 additions & 0 deletions botstory/integrations/ga/tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import functools
import json
from .universal_analytics.tracker import Tracker

from ...utils import queue


class GAStatistics:
type = 'interface.tracker'
"""
pageview: [ page path ]
event: category, action, [ label [, value ] ]
social: network, action [, target ]
timing: category, variable, time [, label ]
"""

def __init__(self,
tracking_id,
story_tracking_template='{story}/{part}',
new_message_tracking_template='receive: {data}',
):
"""
:param tracking_id: should be like UA-XXXXX-Y
"""
self.tracking_id = tracking_id
self.story_tracking_template = story_tracking_template
self.new_message_tracking_template = new_message_tracking_template

def get_tracker(self, user):
return Tracker(
account=self.tracking_id,
client_id=user and user['_id'],
)

def event(self, user,
event_category=None,
event_action=None,
event_label=None,
event_value=None,
):
queue.add(
functools.partial(self.get_tracker(user).send,
'event', event_category, event_action, event_label, event_value
)
)

def story(self, user, story_name, story_part_name):
queue.add(
functools.partial(self.get_tracker(user).send,
'pageview', self.story_tracking_template.format(story=story_name,
part=story_part_name),
)
)

def new_message(self, user, data):
queue.add(
functools.partial(self.get_tracker(user).send,
'pageview', self.new_message_tracking_template.format(data=json.dumps(data)),
)
)

def new_user(self, user):
queue.add(
functools.partial(self.get_tracker(user).send,
'event',
'new_user', 'start', 'new user starts chat'
)
)

0 comments on commit de0606a

Please sign in to comment.