def test_create_action(self, patch_capture, *args): Event.objects.create( team=self.team, event="$autocapture", elements=[ Element(tag_name="button", text="sign up NOW"), Element(tag_name="div") ], ) response = self.client.post( "/api/action/", data={ "name": "user signed up", "steps": [{ "text": "sign up", "selector": "div > button", "url": "/signup", "isNew": "asdf" }], }, HTTP_ORIGIN="http://testserver", ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.json()["is_calculating"], False) self.assertIn("last_calculated_at", response.json()) action = Action.objects.get() self.assertEqual(action.name, "user signed up") self.assertEqual(action.team, self.team) self.assertEqual(action.steps.get().selector, "div > button") self.assertEqual(response.json()["steps"][0]["text"], "sign up") self.assertEqual(response.json()["steps"][0]["url"], "/signup") self.assertNotIn("isNew", response.json()["steps"][0]) # Assert analytics are sent patch_capture.assert_called_once_with( self.user.distinct_id, "action created", { "post_to_slack": False, "name_length": 14, "custom_slack_message_format": False, "event_count_precalc": 0, "step_count": 1, "match_text_count": 1, "match_href_count": 0, "match_selector_count": 1, "match_url_count": 1, "has_properties": False, "deleted": False, }, )
def test_element_automatic_order(self): elements = [ Element(tag_name="a", href="https://posthog.com/about", text="click here"), Element(tag_name="span"), Element(tag_name="div"), ] ElementGroup.objects.create(team=self.team, elements=elements) self.assertEqual(elements[0].order, 0) self.assertEqual(elements[1].order, 1) self.assertEqual(elements[2].order, 2)
def _movie_event(self, distinct_id: str): sign_up = Event.objects.create( distinct_id=distinct_id, team=self.team, elements=[ Element(tag_name='a', attr_class=['watch_movie', 'play'], text='Watch now', attr_id='something', href='/movie', order=0), Element(tag_name='div', href='/movie', order=1) ])
def test_event_property_values(self): group = ElementGroup.objects.create( team=self.team, elements=[Element(tag_name="a", href="https://posthog.com/about", text="click here")], ) team2 = Team.objects.create() ElementGroup.objects.create(team=team2, elements=[Element(tag_name="bla")]) response = self.client.get("/api/element/values/?key=tag_name").json() self.assertEqual(response[0]["name"], "a") self.assertEqual(len(response), 1) response = self.client.get("/api/element/values/?key=text&value=click").json() self.assertEqual(response[0]["name"], "click here") self.assertEqual(len(response), 1)
def test_create_action_event_with_space(self, patch_capture, *args): Event.objects.create( team=self.team, event="test_event ", # notice trailing space elements=[Element(tag_name="button", text="sign up NOW"), Element(tag_name="div")], ) response = self.client.post( "/api/action/", data={"name": "test event", "steps": [{"event": "test_event "}],}, HTTP_ORIGIN="http://testserver", ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) action = Action.objects.get() self.assertEqual(action.steps.get().event, "test_event ")
def test_live_action_events(self): action_sign_up = Action.objects.create(team=self.team, name='signed up') ActionStep.objects.create(action=action_sign_up, tag_name='button', text='Sign up!') # 2 steps that match same element might trip stuff up ActionStep.objects.create(action=action_sign_up, tag_name='button', text='Sign up!') action_credit_card = Action.objects.create(team=self.team, name='paid') ActionStep.objects.create(action=action_credit_card, tag_name='button', text='Pay $10') action_watch_movie = Action.objects.create(team=self.team, name='watch movie') ActionStep.objects.create(action=action_watch_movie, text='Watch now', selector="div > a.watch_movie") # events person_stopped_after_signup = Person.objects.create(distinct_ids=["stopped_after_signup"], team=self.team) event_sign_up_1 = self._signup_event('stopped_after_signup') person_stopped_after_pay = Person.objects.create(distinct_ids=["stopped_after_pay"], team=self.team) self._signup_event('stopped_after_pay') self._pay_event('stopped_after_pay') self._movie_event('stopped_after_pay') # Test filtering of deleted actions deleted_action_watch_movie = Action.objects.create(team=self.team, name='watch movie', deleted=True) ActionStep.objects.create(action=deleted_action_watch_movie, text='Watch now', selector="div > a.watch_movie") deleted_action_watch_movie.calculate_events() # non matching events non_matching = Event.objects.create(distinct_id='stopped_after_pay', properties={'$current_url': 'http://whatever.com'}, team=self.team, elements=[ Element(tag_name='blabla', href='/moviedd', order=0), Element(tag_name='blabla', href='/moviedd', order=1) ]) last_event = Event.objects.create(distinct_id='stopped_after_pay', properties={'$current_url': 'http://whatever.com'}, team=self.team) # with self.assertNumQueries(8): response = self.client.get('/api/event/actions/').json() self.assertEqual(len(response['results']), 4) self.assertEqual(response['results'][3]['event']['id'], event_sign_up_1.pk) self.assertEqual(response['results'][3]['action']['id'], action_sign_up.pk) self.assertEqual(response['results'][3]['action']['name'], 'signed up') self.assertEqual(response['results'][2]['action']['id'], action_sign_up.pk) self.assertEqual(response['results'][1]['action']['id'], action_credit_card.pk) self.assertEqual(response['results'][0]['action']['id'], action_watch_movie.pk) # test after sign_up_event = self._signup_event('stopped_after_pay') response = self.client.get('/api/event/actions/?after=%s' % last_event.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')).json() self.assertEqual(len(response['results']), 1)
def test_filter_events(self): _create_person( properties={"email": "*****@*****.**"}, team=self.team, distinct_ids=["2", "some-random-uid"], is_identified=True, ) _create_event( event="$autocapture", team=self.team, distinct_id="2", properties={"$ip": "8.8.8.8"}, elements=[ Element(tag_name="button", text="something"), Element(tag_name="div") ], ) _create_event(event="$pageview", team=self.team, distinct_id="some-random-uid", properties={"$ip": "8.8.8.8"}) _create_event(event="$pageview", team=self.team, distinct_id="some-other-one", properties={"$ip": "8.8.8.8"}) expected_queries = ( 8 # Django session, PostHog user, PostHog team, PostHog org membership, 2x team(?), person and distinct id ) with self.assertNumQueries(expected_queries): response = self.client.get( f"/api/projects/{self.team.id}/events/?distinct_id=2").json() self.assertEqual( response["results"][0]["person"], { "distinct_ids": ["2"], "is_identified": True, "properties": { "email": "*****@*****.**" } }, ) self.assertEqual(response["results"][0]["elements"][0]["tag_name"], "button") self.assertEqual(response["results"][0]["elements"][0]["order"], 0) self.assertEqual(response["results"][0]["elements"][1]["order"], 1)
def test_element_stats(self): elements = [ Element(tag_name='a', href='https://posthog.com/about', text='click here', order=0), Element(tag_name='div', href='https://posthog.com/about', text='click here', order=1) ] event1 = Event.objects.create( team=self.team, elements=elements, event='$autocapture', properties={'$current_url': 'http://example.com/demo'}) Event.objects.create( team=self.team, elements=elements, event='$autocapture', properties={'$current_url': 'http://example.com/demo'}) # make sure we only load last 7 days by default Event.objects.create( timestamp=now() - relativedelta(days=8), team=self.team, elements=elements, event='$autocapture', properties={'$current_url': 'http://example.com/demo'}) Event.objects.create( team=self.team, event='$autocapture', properties={'$current_url': 'http://example.com/something_else'}, elements=[Element(tag_name='img', order=0)]) with self.assertNumQueries(6): response = self.client.get('/api/element/stats/').json() self.assertEqual(response[0]['count'], 2) self.assertEqual(response[0]['hash'], event1.elements_hash) self.assertEqual(response[0]['elements'][0]['tag_name'], 'a') self.assertEqual(response[1]['count'], 1) response = self.client.get( '/api/element/stats/?properties=%s' % json.dumps([{ 'key': '$current_url', 'value': 'http://example.com/demo' }])).json()
def test_person_with_different_distinct_id(self): action_watch_movie = Action.objects.create(team=self.team, name="watched movie") ActionStep.objects.create(action=action_watch_movie, tag_name="a", href="/movie") person = Person.objects.create(distinct_ids=["anonymous_user", "is_now_signed_up"], team=self.team) event_watched_movie_anonymous = Event.objects.create( distinct_id="anonymous_user", team=self.team, elements=[Element(tag_name="a", href="/movie")], ) event_watched_movie = Event.objects.create( distinct_id="is_now_signed_up", team=self.team, elements=[Element(tag_name="a", href="/movie")], ) events = Event.objects.filter_by_action(action_watch_movie) self.assertEqual(events[0], event_watched_movie) self.assertEqual(events[0].person_id, person.pk)
def test_with_class_with_escaped_slashes(self): Person.objects.create(distinct_ids=["whatever"], team=self.team) action1 = Action.objects.create(team=self.team) ActionStep.objects.create(action=action1, selector="a.na\\\\\\\\\\\\v-link\\:b\\@ld", tag_name="a") event1 = Event.objects.create( team=self.team, distinct_id="whatever", elements=[ Element(tag_name="span", attr_class=None), Element(tag_name="a", attr_class=["na\\\\\\v-link:b@ld"]), ], ) events = Event.objects.filter_by_action(action1) self.assertEqual(events[0], event1) self.assertEqual(len(events), 1)
def _capture( ip: str, site_url: str, team_id: int, event: str, distinct_id: str, properties: Dict, timestamp: Union[datetime.datetime, str], ) -> None: elements = properties.get("$elements") elements_list = None if elements: del properties["$elements"] elements_list = [ Element( text=el["$el_text"][0:400] if el.get("$el_text") else None, tag_name=el["tag_name"], href=el["attr__href"][0:2048] if el.get("attr__href") else None, attr_class=el["attr__class"].split(" ") if el.get("attr__class") else None, attr_id=el.get("attr__id"), nth_child=el.get("nth_child"), nth_of_type=el.get("nth_of_type"), attributes={ key: value for key, value in el.items() if key.startswith("attr__") }, order=index, ) for index, el in enumerate(elements) ] team = Team.objects.only("slack_incoming_webhook", "event_names", "event_properties", "anonymize_ips").get(pk=team_id) if not team.anonymize_ips: properties["$ip"] = ip Event.objects.create(event=event, distinct_id=distinct_id, properties=properties, team=team, site_url=site_url, **({ "timestamp": timestamp } if timestamp else {}), **({ "elements": elements_list } if elements_list else {})) _store_names_and_properties(team=team, event=event, properties=properties) if not Person.objects.distinct_ids_exist(team_id=team_id, distinct_ids=[str(distinct_id)]): # Catch race condition where in between getting and creating, another request already created this user. try: Person.objects.create(team_id=team_id, distinct_ids=[str(distinct_id)]) except IntegrityError: pass
def test_filter_events_by_url_exact(self): Person.objects.create(distinct_ids=['whatever'], team=self.team) event1 = Event.objects.create(team=self.team, distinct_id='whatever') event2 = Event.objects.create( team=self.team, distinct_id='whatever', properties={'$current_url': 'https://posthog.com/feedback/123'}, elements=[ Element(tag_name='div', text='some_other_text', nth_child=0, nth_of_type=0, order=1) ]) action1 = Action.objects.create(team=self.team) ActionStep.objects.create(action=action1, url='https://posthog.com/feedback/123', url_matching=ActionStep.EXACT) ActionStep.objects.create(action=action1, href='/a-url-2') action2 = Action.objects.create(team=self.team) ActionStep.objects.create(action=action2, url='123', url_matching=ActionStep.CONTAINS) events = Event.objects.filter_by_action(action1) self.assertEqual(events[0], event2) self.assertEqual(len(events), 1) events = Event.objects.filter_by_action(action2) self.assertEqual(events[0], event2) self.assertEqual(len(events), 1)
def test_filter_events_by_url(self): Person.objects.create(distinct_ids=["whatever"], team=self.team) action1 = Action.objects.create(team=self.team) ActionStep.objects.create( action=action1, url="https://posthog.com/feedback/123", url_matching=ActionStep.EXACT, ) ActionStep.objects.create(action=action1, href="/a-url-2") action2 = Action.objects.create(team=self.team) ActionStep.objects.create(action=action2, url="123", url_matching=ActionStep.CONTAINS) action3 = Action.objects.create(team=self.team) ActionStep.objects.create( action=action3, url="https://posthog.com/%/123", url_matching=ActionStep.CONTAINS, ) action4 = Action.objects.create(team=self.team) ActionStep.objects.create( action=action4, url="/123$", url_matching=ActionStep.REGEX, ) event1 = Event.objects.create(team=self.team, distinct_id="whatever") event2 = Event.objects.create( team=self.team, distinct_id="whatever", properties={"$current_url": "https://posthog.com/feedback/123"}, elements=[ Element( tag_name="div", text="some_other_text", nth_child=0, nth_of_type=0, order=1, ) ], ) events = Event.objects.filter_by_action(action1) self.assertEqual(events[0], event2) self.assertEqual(len(events), 1) events = Event.objects.filter_by_action(action2) self.assertEqual(events[0], event2) self.assertEqual(len(events), 1) events = Event.objects.filter_by_action(action3) self.assertEqual(events[0], event2) self.assertEqual(len(events), 1) events = Event.objects.filter_by_action(action4) self.assertEqual(events[0], event2) self.assertEqual(len(events), 1)
def _movie_event(self, **kwargs): event_factory( team=self.team, event="$autocapture", elements=[Element(nth_of_type=1, nth_child=0, tag_name="a", href="/movie")], **kwargs )
def _pay_event(self, **kwargs): event_factory( team=self.team, event="$autocapture", elements=[Element(nth_of_type=1, nth_child=0, tag_name="button", text="Pay $10")], **kwargs )
def _signup_event(self, distinct_id: str): sign_up = Event.objects.create( distinct_id=distinct_id, team=self.team, elements=[Element(tag_name="button", text="Sign up!")], ) return sign_up
def _capture(ip: str, site_url: str, team_id: int, event: str, distinct_id: str, properties: Dict, timestamp: Union[datetime.datetime, str]) -> None: elements = properties.get('$elements') elements_list = None if elements: del properties['$elements'] elements_list = [ Element( text=el.get('$el_text'), tag_name=el['tag_name'], href=el.get('attr__href'), attr_class=el['attr__class'].split(' ') if el.get('attr__class') else None, attr_id=el.get('attr__id'), nth_child=el.get('nth_child'), nth_of_type=el.get('nth_of_type'), attributes={key: value for key, value in el.items() if key.startswith('attr__')}, order=index ) for index, el in enumerate(elements) ] properties["$ip"] = ip Event.objects.create( event=event, distinct_id=distinct_id, properties=properties, team_id=team_id, site_url=site_url, **({'timestamp': timestamp} if timestamp else {}), **({'elements': elements_list} if elements_list else {}) ) _store_names_and_properties(team_id=team_id, event=event, properties=properties) # try to create a new person try: Person.objects.create(team_id=team_id, distinct_ids=[str(distinct_id)]) except IntegrityError: pass # person already exists, which is fine
def _capture(request, team: Team, event: str, distinct_id: str, properties: Dict, timestamp: Optional[str]=None) -> None: elements = properties.get('$elements') elements_list = None if elements: del properties['$elements'] elements_list = [ Element( text=el.get('$el_text'), tag_name=el['tag_name'], href=el.get('attr__href'), attr_class=el['attr__class'].split(' ') if el.get('attr__class') else None, attr_id=el.get('attr__id'), nth_child=el.get('nth_child'), nth_of_type=el.get('nth_of_type'), attributes={key: value for key, value in el.items() if key.startswith('attr__')}, order=index ) for index, el in enumerate(elements) ] db_event = Event.objects.create( event=event, distinct_id=distinct_id, properties=properties, ip=get_ip_address(request), team=team, **({'timestamp': timestamp} if timestamp else {}), **({'elements': elements_list} if elements_list else {}) ) # try to create a new person try: Person.objects.create(team=team, distinct_ids=[str(distinct_id)], is_user=request.user if not request.user.is_anonymous else None) except IntegrityError: pass # person already exists, which is fine
def test_filter_events(self): person = Person.objects.create( properties={"email": "*****@*****.**"}, team=self.team, distinct_ids=["2", "some-random-uid"], ) event1 = Event.objects.create( team=self.team, distinct_id="2", properties={"$ip": "8.8.8.8"}, elements=[Element(tag_name="button", text="something")], ) Event.objects.create(team=self.team, distinct_id="some-random-uid", properties={"$ip": "8.8.8.8"}) Event.objects.create(team=self.team, distinct_id="some-other-one", properties={"$ip": "8.8.8.8"}) with self.assertNumQueries(11): response = self.client.get("/api/event/?distinct_id=2").json() self.assertEqual(response["results"][0]["person"], "*****@*****.**") self.assertEqual(response["results"][0]["elements"][0]["tag_name"], "button")
def _signup_event(self, distinct_id: str): sign_up = Event.objects.create( distinct_id=distinct_id, team=self.team, event='$autocapture', elements=[Element(tag_name='button', text='Sign up!')]) return sign_up
def _movie_event(self, distinct_id: str): sign_up = event_factory( event="$autocapture", distinct_id=distinct_id, team=self.team, elements=[ Element( tag_name="a", attr_class=["watch_movie", "play"], text="Watch now", attr_id="something", href="/movie", ), Element(tag_name="div", href="/movie"), ], ) return sign_up
def _signup_event(self, distinct_id: str): sign_up = event_factory( event="$autocapture", distinct_id=distinct_id, team=self.team, elements=[Element(tag_name="button", text="Sign up!")], ) return sign_up
def _movie_event(self, distinct_id: str): event = Event.objects.create( distinct_id=distinct_id, team=self.team, event="$autocapture", elements=[ Element( tag_name="a", attr_class=["watch_movie", "play"], text="Watch now", attr_id="something", href="/movie", ), Element(tag_name="div", href="/movie"), ], ) return event
def _movie_event(self, distinct_id: str): sign_up = Event.objects.create( distinct_id=distinct_id, team=self.team, elements=[ Element( tag_name="a", attr_class=["watch_movie", "play"], text="Watch now", attr_id="something", href="/movie", order=0, ), Element(tag_name="div", href="/movie", order=1), ], ) return sign_up
def test_filter_events(self): person_factory( properties={"email": "*****@*****.**"}, team=self.team, distinct_ids=["2", "some-random-uid"], is_identified=True, ) event_factory( event="$autocapture", team=self.team, distinct_id="2", properties={"$ip": "8.8.8.8"}, elements=[ Element(tag_name="button", text="something"), Element(tag_name="div") ], ) event_factory(event="$pageview", team=self.team, distinct_id="some-random-uid", properties={"$ip": "8.8.8.8"}) event_factory(event="$pageview", team=self.team, distinct_id="some-other-one", properties={"$ip": "8.8.8.8"}) expected_queries = 4 if settings.PRIMARY_DB == RDBMS.CLICKHOUSE else 11 with self.assertNumQueries(expected_queries): response = self.client.get("/api/event/?distinct_id=2").json() self.assertEqual( response["results"][0]["person"], { "distinct_ids": ["2"], "is_identified": True, "properties": { "email": "*****@*****.**" } }, ) self.assertEqual(response["results"][0]["elements"][0]["tag_name"], "button") self.assertEqual(response["results"][0]["elements"][0]["order"], 0) self.assertEqual(response["results"][0]["elements"][1]["order"], 1)
def test_element_class_set_to_none(self): action_user_paid = Action.objects.create(team=self.team, name='user paid') ActionStep.objects.create(action=action_user_paid, selector='a.something') Person.objects.create(distinct_ids=["user_paid"], team=self.team) event = Event.objects.create(event='$autocapture', distinct_id='user_paid', team=self.team, elements=[ Element(tag_name='a', attr_class=None, order=0) ]) # This would error when attr_class wasn't set. self.assertEqual(event.actions, [])
def test_create_elements(self): elements = [ Element(tag_name='button', text='Sign up!'), Element(tag_name='div') ] group1 = ElementGroup.objects.create(team=self.team, elements=elements) elements = Element.objects.all() self.assertEqual(elements[0].tag_name, 'button') self.assertEqual(elements[1].tag_name, 'div') elements = [ Element(tag_name='button', text='Sign up!'), # make sure we remove events if we can Element(tag_name='div', event=Event.objects.create(team=self.team)) ] group2 = ElementGroup.objects.create(team=self.team, elements=elements) self.assertEqual(Element.objects.count(), 2) self.assertEqual(group1.hash, group2.hash)
def test_element_stats(self): elements = [ Element(tag_name="a", href="https://posthog.com/about", text="click here", order=0,), Element(tag_name="div", href="https://posthog.com/about", text="click here", order=1,), ] event1 = Event.objects.create( team=self.team, elements=elements, event="$autocapture", properties={"$current_url": "http://example.com/demo"}, ) Event.objects.create( team=self.team, elements=elements, event="$autocapture", properties={"$current_url": "http://example.com/demo"}, ) # make sure we only load last 7 days by default Event.objects.create( timestamp=now() - relativedelta(days=8), team=self.team, elements=elements, event="$autocapture", properties={"$current_url": "http://example.com/demo"}, ) Event.objects.create( team=self.team, event="$autocapture", properties={"$current_url": "http://example.com/something_else"}, elements=[Element(tag_name="img", order=0)], ) with self.assertNumQueries(6): response = self.client.get("/api/element/stats/").json() self.assertEqual(response[0]["count"], 2) self.assertEqual(response[0]["hash"], event1.elements_hash) self.assertEqual(response[0]["elements"][0]["tag_name"], "a") self.assertEqual(response[1]["count"], 1) response = self.client.get( "/api/element/stats/?properties=%s" % json.dumps([{"key": "$current_url", "value": "http://example.com/demo"}]) ).json()
def test_with_class(self): Person.objects.create(distinct_ids=['whatever'], team=self.team) action1 = Action.objects.create(team=self.team) ActionStep.objects.create(action=action1, selector='a.nav-link.active', tag_name='a') event1 = Event.objects.create( team=self.team, distinct_id="whatever", elements=[ Element(tag_name='span', attr_class=None, order=0), Element(tag_name='a', attr_class=['active', 'nav-link'], order=1) ]) events = Event.objects.filter_by_action(action1) self.assertEqual(events[0], event1) self.assertEqual(len(events), 1)
def test_with_class_with_escaped_slashes(self): _create_person(distinct_ids=["whatever"], team=self.team) action1 = Action.objects.create(team=self.team) ActionStep.objects.create( event="$autocapture", action=action1, selector="a.na\\\\\\\\\\\\v-link\\:b\\@ld", tag_name="a" ) event1 = _create_event( event="$autocapture", team=self.team, distinct_id="whatever", elements=[ Element(tag_name="span", attr_class=None), Element(tag_name="a", attr_class=["na\\\\\\v-link:b@ld"]), ], ) events = _get_events_for_action(action1) self.assertEqual(events[0].pk, event1.pk) self.assertEqual(len(events), 1)