def _setup_basic_test(self): filters = { "events": [ {"id": "user signed up", "type": "events", "order": 0}, {"id": "paid", "type": "events", "order": 1}, ], "insight": INSIGHT_FUNNELS, "date_from": "2020-01-01", "date_to": "2020-01-14", "funnel_correlation_type": "events", } filter = Filter(data=filters) success_target_persons = [] failure_target_persons = [] events_by_person = {} for i in range(10): person_id = f"user_{i}" person = _create_person(distinct_ids=[person_id], team_id=self.team.pk) events_by_person[person_id] = [{"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14)}] if i % 2 == 0: events_by_person[person_id].append( {"event": "positively_related", "timestamp": datetime(2020, 1, 3, 14)} ) success_target_persons.append(str(person.uuid)) events_by_person[person_id].append({"event": "paid", "timestamp": datetime(2020, 1, 4, 14)}) for i in range(10, 20): person_id = f"user_{i}" person = _create_person(distinct_ids=[person_id], team_id=self.team.pk) events_by_person[person_id] = [{"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14)}] if i % 2 == 0: events_by_person[person_id].append( {"event": "negatively_related", "timestamp": datetime(2020, 1, 3, 14)} ) failure_target_persons.append(str(person.uuid)) # One positively_related as failure person_fail_id = f"user_fail" person_fail = _create_person(distinct_ids=[person_fail_id], team_id=self.team.pk) events_by_person[person_fail_id] = [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14)}, {"event": "positively_related", "timestamp": datetime(2020, 1, 3, 14)}, ] # One negatively_related as success person_success_id = f"user_succ" person_succ = _create_person(distinct_ids=[person_success_id], team_id=self.team.pk) events_by_person[person_success_id] = [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14)}, {"event": "negatively_related", "timestamp": datetime(2020, 1, 3, 14)}, {"event": "paid", "timestamp": datetime(2020, 1, 4, 14)}, ] journeys_for(events_by_person, self.team) return filter, success_target_persons, failure_target_persons, person_fail, person_succ
def test_breakdown_by_group_props(self): self._create_groups() journey = { "person1": [ { "event": "sign up", "timestamp": datetime(2020, 1, 2, 12), "properties": {"$group_0": "org:5"}, "group0_properties": {"industry": "finance"}, }, { "event": "sign up", "timestamp": datetime(2020, 1, 2, 13), "properties": {"$group_0": "org:6"}, "group0_properties": {"industry": "technology"}, }, { "event": "sign up", "timestamp": datetime(2020, 1, 2, 15), "properties": {"$group_0": "org:7", "$group_1": "company:10"}, "group0_properties": {"industry": "finance"}, "group1_properties": {"industry": "finance"}, }, ], } journeys_for(events_by_person=journey, team=self.team) filter = Filter( data={ "date_from": "2020-01-01T00:00:00Z", "date_to": "2020-01-12", "breakdown": "industry", "breakdown_type": "group", "breakdown_group_type_index": 0, "events": [{"id": "sign up", "name": "sign up", "type": "events", "order": 0,}], } ) response = Trends().run(filter, self.team,) self.assertEqual(len(response), 2) self.assertEqual(response[0]["breakdown_value"], "finance") self.assertEqual(response[0]["count"], 2) self.assertEqual(response[1]["breakdown_value"], "technology") self.assertEqual(response[1]["count"], 1) filter = filter.with_data( {"breakdown_value": "technology", "date_from": "2020-01-02T00:00:00Z", "date_to": "2020-01-03"} ) entity = Entity({"id": "sign up", "name": "sign up", "type": "events", "order": 0,}) res = self._get_trend_people(filter, entity) self.assertEqual(res[0]["distinct_ids"], ["person1"])
def test_test_account_filters_with_groups(self): self.team.test_account_filters = [ {"key": "key", "type": "group", "value": "value", "group_type_index": 0}, ] self.team.save() GroupTypeMapping.objects.create(team=self.team, group_type="organization", group_type_index=0) create_group(self.team.pk, group_type_index=0, group_key="in", properties={"key": "value"}) create_group(self.team.pk, group_type_index=0, group_key="out", properties={"key": "othervalue"}) with freeze_time("2020-01-11T12:00:00Z"): Person.objects.create(distinct_ids=["person1"], team_id=self.team.pk) with freeze_time("2020-01-09T12:00:00Z"): Person.objects.create(distinct_ids=["person2"], team_id=self.team.pk) journeys_for( { "person1": [ {"event": "$pageview", "timestamp": datetime(2020, 1, 11, 12), "properties": {"$group_0": "out"},}, ], "person2": [ {"event": "$pageview", "timestamp": datetime(2020, 1, 9, 12), "properties": {"$group_0": "in"},}, {"event": "$pageview", "timestamp": datetime(2020, 1, 12, 12), "properties": {"$group_0": "in"},}, {"event": "$pageview", "timestamp": datetime(2020, 1, 15, 12), "properties": {"$group_0": "in"},}, ], }, self.team, ) result = Trends().run( Filter( data={ "date_from": "2020-01-12T00:00:00Z", "date_to": "2020-01-19T00:00:00Z", "events": [{"id": "$pageview", "type": "events", "order": 0}], "shown_as": TRENDS_LIFECYCLE, FILTER_TEST_ACCOUNTS: True, }, team=self.team, ), self.team, ) self.assertLifecycleResults( result, [ {"status": "dormant", "data": [0, -1, 0, 0, -1, 0, 0, 0]}, {"status": "new", "data": [0, 0, 0, 0, 0, 0, 0, 0]}, {"status": "resurrecting", "data": [1, 0, 0, 1, 0, 0, 0, 0]}, {"status": "returning", "data": [0, 0, 0, 0, 0, 0, 0, 0]}, ], )
def test_unordered_funnel_with_groups(self): GroupTypeMapping.objects.create(team=self.team, group_type="organization", group_type_index=0) GroupTypeMapping.objects.create(team=self.team, group_type="company", group_type_index=1) create_group(team_id=self.team.pk, group_type_index=0, group_key="org:5", properties={"industry": "finance"}) create_group(team_id=self.team.pk, group_type_index=0, group_key="org:6", properties={"industry": "technology"}) create_group(team_id=self.team.pk, group_type_index=1, group_key="company:1", properties={}) create_group(team_id=self.team.pk, group_type_index=1, group_key="company:2", properties={}) events_by_person = { "user_1": [ {"event": "user signed up", "timestamp": datetime(2020, 1, 3, 14), "properties": {"$group_0": "org:5"}}, { # same person, different group, so should count as different step 1 in funnel "event": "user signed up", "timestamp": datetime(2020, 1, 10, 14), "properties": {"$group_0": "org:6"}, }, ], "user_2": [ { # different person, same group, so should count as step two in funnel "event": "paid", "timestamp": datetime(2020, 1, 2, 14), "properties": {"$group_0": "org:5"}, } ], } journeys_for(events_by_person, self.team) params = FunnelRequest( events=json.dumps( [ EventPattern(id="user signed up", type="events", order=0), EventPattern(id="paid", type="events", order=1), ] ), date_from="2020-01-01", date_to="2020-01-14", aggregation_group_type_index=0, funnel_order_type="unordered", insight=INSIGHT_FUNNELS, ) result = get_funnel_ok(self.client, self.team.pk, params) assert result["user signed up"]["count"] == 2 assert result["paid"]["count"] == 1 assert result["paid"]["average_conversion_time"] == 86400 actors = get_funnel_actors_ok(self.client, result["user signed up"]["converted_people_url"]) assert len(actors) == 2
def _setup_returning_lifecycle_data(self, days): with freeze_time("2019-01-01T12:00:00Z"): Person.objects.create(distinct_ids=["person1"], team_id=self.team.pk) journeys_for( { "person1": [ {"event": "$pageview", "timestamp": (now() - timedelta(days=n)).strftime("%Y-%m-%d %H:%M:%S.%f")} for n in range(days) ], }, self.team, create_people=False, )
def test_timezones(self, patch_feature_enabled): journeys_for( { "person1": [ { "event": "$pageview", "timestamp": datetime(2021, 5, 2, 1), }, # this time will fall on 5/1 in US Pacific {"event": "$pageview", "timestamp": datetime(2021, 5, 2, 9)}, {"event": "$pageview", "timestamp": datetime(2021, 5, 4, 3)}, ], }, self.team, ) data = ClickhouseStickiness().run( filter=StickinessFilter( data={ "shown_as": "Stickiness", "date_from": "2021-05-01", "date_to": "2021-05-15", "events": [{"id": "$pageview"}], }, team=self.team, ), team=self.team, ) self.assertEqual(data[0]["days"], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) self.assertEqual(data[0]["data"], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) self.team.timezone = "US/Pacific" self.team.save() data_pacific = ClickhouseStickiness().run( filter=StickinessFilter( data={ "shown_as": "Stickiness", "date_from": "2021-05-01", "date_to": "2021-05-15", "events": [{"id": "$pageview"}], }, team=self.team, ), team=self.team, ) self.assertEqual(data_pacific[0]["days"], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) self.assertEqual(data_pacific[0]["data"], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
def test_funnel_group_aggregation_with_groups_entity_filtering(self): self._create_groups() events_by_person = { "user_1": [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14), "properties": {"$group_0": "org:5"}} ], "user_2": [ { # different person, same group, so should count as step two in funnel "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": {"$group_0": "org:5"}, }, ], "user_3": [ { # different person, different group, so should be discarded from step 1 in funnel "event": "user signed up", "timestamp": datetime(2020, 1, 10, 14), "properties": {"$group_0": "org:6"}, }, ], } journeys_for(events_by_person, self.team) params = FunnelRequest( events=json.dumps([ EventPattern(id="user signed up", type="events", order=0, properties={"$group_0": "org:5"}), EventPattern(id="paid", type="events", order=1), ]), date_from="2020-01-01", date_to="2020-01-14", aggregation_group_type_index=0, insight=INSIGHT_FUNNELS, ) result = get_funnel_ok(self.client, self.team.pk, params) assert result["user signed up"]["count"] == 1 assert result["paid"]["count"] == 1 assert result["paid"]["average_conversion_time"] == 86400 actors = get_funnel_actors_ok( self.client, result["user signed up"]["converted_people_url"]) actor_ids = [str(val["id"]) for val in actors] assert actor_ids == ["org:5"]
def test_people_arent_returned_multiple_times(self): people = journeys_for( { "user_1": [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14)}, {"event": "positively_related", "timestamp": datetime(2020, 1, 3, 14)}, # duplicate event {"event": "positively_related", "timestamp": datetime(2020, 1, 3, 14)}, {"event": "paid", "timestamp": datetime(2020, 1, 4, 14)}, ] }, self.team, ) filter = Filter( data={ "events": [ {"id": "user signed up", "type": "events", "order": 0}, {"id": "paid", "type": "events", "order": 1}, ], "insight": INSIGHT_FUNNELS, "date_from": "2020-01-01", "date_to": "2020-01-14", "funnel_correlation_type": "events", "funnel_correlation_person_entity": {"id": "positively_related", "type": "events"}, "funnel_correlation_person_converted": "TrUe", } ) _, serialized_actors = FunnelCorrelationActors(filter, self.team).get_actors() self.assertCountEqual([str(val["id"]) for val in serialized_actors], [str(people["user_1"].uuid)])
def test_lifecycle_edge_cases(self): # This test tests behavior when created_at is different from first matching event and dormant/resurrecting/returning logic with freeze_time("2020-01-11T12:00:00Z"): Person.objects.create(distinct_ids=["person1"], team_id=self.team.pk) journeys_for( { "person1": [ {"event": "$pageview", "timestamp": datetime(2020, 1, 12, 12),}, {"event": "$pageview", "timestamp": datetime(2020, 1, 13, 12),}, {"event": "$pageview", "timestamp": datetime(2020, 1, 15, 12),}, {"event": "$pageview", "timestamp": datetime(2020, 1, 16, 12),}, ], }, self.team, ) result = Trends().run( Filter( data={ "date_from": "2020-01-11T00:00:00Z", "date_to": "2020-01-18T00:00:00Z", "events": [{"id": "$pageview", "type": "events", "order": 0}], "shown_as": TRENDS_LIFECYCLE, }, team=self.team, ), self.team, ) self.assertLifecycleResults( result, [ {"status": "dormant", "data": [0, 0, 0, -1, 0, 0, -1, 0]}, {"status": "new", "data": [0, 0, 0, 0, 0, 0, 0, 0]}, {"status": "resurrecting", "data": [0, 1, 0, 0, 1, 0, 0, 0]}, {"status": "returning", "data": [0, 0, 1, 0, 0, 1, 0, 0]}, ], )
def test_event_correlation_endpoint_does_not_include_funnel_steps(self): with freeze_time("2020-01-01"): self.client.force_login(self.user) # Add Person1 with only the funnel steps involved events = { "Person 1": [ { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "some waypoint", "timestamp": datetime(2020, 1, 2) }, { "event": "", "timestamp": datetime(2020, 1, 3) }, ], # We need atleast 1 success and failure to return a result "Person 2": [ { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "some waypoint", "timestamp": datetime(2020, 1, 2) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } # '' is a weird event name to have, but if it exists, our duty to report it journeys_for(events_by_person=events, team=self.team) # We need to make sure we clear the cache other tests that have run # done interfere with this test cache.clear() odds = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="some waypoint"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) assert odds == { "is_cached": False, "last_refresh": "2020-01-01T00:00:00Z", "result": { "events": [{ "correlation_type": "failure", "event": { "event": "", "elements": [], "properties": {} }, "failure_count": 1, "odds_ratio": 1 / 4, "success_count": 0, "success_people_url": ANY, "failure_people_url": ANY, }], "skewed": False, }, }
def test_funnel_aggregation_with_groups_with_cohort_filtering(self): GroupTypeMapping.objects.create(team=self.team, group_type="organization", group_type_index=0) GroupTypeMapping.objects.create(team=self.team, group_type="company", group_type_index=1) create_group(team_id=self.team.pk, group_type_index=0, group_key="org:5", properties={"industry": "finance"}) create_group(team_id=self.team.pk, group_type_index=0, group_key="org:6", properties={"industry": "technology"}) create_group(team_id=self.team.pk, group_type_index=1, group_key="company:1", properties={}) create_group(team_id=self.team.pk, group_type_index=1, group_key="company:2", properties={}) _create_person(distinct_ids=[f"user_1"], team=self.team, properties={"email": "*****@*****.**"}) _create_person(distinct_ids=[f"user_2"], team=self.team, properties={"email": "*****@*****.**"}) _create_person(distinct_ids=[f"user_3"], team=self.team, properties={"email": "*****@*****.**"}) action1 = Action.objects.create(team=self.team, name="action1") ActionStep.objects.create( event="$pageview", action=action1, ) cohort = Cohort.objects.create( team=self.team, groups=[{ "properties": [{ "key": "email", "operator": "icontains", "value": "*****@*****.**", "type": "person" }] }], ) events_by_person = { "user_1": [ {"event": "$pageview", "timestamp": datetime(2020, 1, 2, 14), "properties": {"$group_0": "org:5"}}, {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14), "properties": {"$group_0": "org:5"}}, { "event": "user signed up", # same person, different group, so should count as different step 1 in funnel "timestamp": datetime(2020, 1, 10, 14), "properties": {"$group_0": "org:6"}, }, ], "user_2": [ { # different person, same group, so should count as step two in funnel "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": {"$group_0": "org:5"}, }, ], "user_3": [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14), "properties": {"$group_0": "org:7"}}, { # person not in cohort so should be filtered out "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": {"$group_0": "org:7"}, }, ], } journeys_for(events_by_person, self.team) cohort.calculate_people_ch(pending_version=0) filters = { "events": [ { "id": "user signed up", "type": "events", "order": 0, "properties": [{ "type": "precalculated-cohort", "key": "id", "value": cohort.pk }], }, { "id": "paid", "type": "events", "order": 1 }, ], "insight": INSIGHT_FUNNELS, "date_from": "2020-01-01", "date_to": "2020-01-14", "aggregation_group_type_index": 0, } filter = Filter(data=filters) funnel = ClickhouseFunnel(filter, self.team) result = funnel.run() self.assertEqual(result[0]["name"], "user signed up") self.assertEqual(result[0]["count"], 2) self.assertEqual(result[1]["name"], "paid") self.assertEqual(result[1]["count"], 1)
def test_basic_secondary_metric_results(self): journeys_for( { # For a trend pageview metric "person1": [ { "event": "$pageview", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test" }, }, ], "person2": [ { "event": "$pageview", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "control" } }, { "event": "$pageview", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "control" } }, ], "person3": [ { "event": "$pageview", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "control" } }, ], # doesn't have feature set "person_out_of_control": [ { "event": "$pageview", "timestamp": "2020-01-03", }, ], "person_out_of_end_date": [ { "event": "$pageview", "timestamp": "2020-08-03", "properties": { "$feature/a-b-test": "control" } }, ], # for a funnel conversion metric "person1_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test" }, }, { "event": "$pageleave_funnel", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test" }, }, ], "person2_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "control" }, }, { "event": "$pageleave_funnel", "timestamp": "2020-01-05", "properties": { "$feature/a-b-test": "control" }, }, ], "person3_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "control" }, }, { "event": "$pageleave_funnel", "timestamp": "2020-01-05", "properties": { "$feature/a-b-test": "control" }, }, ], # doesn't have feature set "person_out_of_control_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-01-03", }, { "event": "$pageleave_funnel", "timestamp": "2020-01-05", }, ], "person_out_of_end_date_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-08-03", "properties": { "$feature/a-b-test": "control" }, }, { "event": "$pageleave_funnel", "timestamp": "2020-08-05", "properties": { "$feature/a-b-test": "control" }, }, ], # non-converters with FF "person4_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "test" }, }, ], "person5_funnel": [ { "event": "$pageview_funnel", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test" }, }, ], }, self.team, ) ff_key = "a-b-test" # generates the FF which should result in the above events^ creation_response = self.client.post( f"/api/projects/{self.team.id}/experiments/", { "name": "Test Experiment", "description": "", "start_date": "2020-01-01T00:00", "end_date": "2020-01-06T00:00", "feature_flag_key": ff_key, "parameters": {}, "secondary_metrics": [ { "name": "trends whatever", "filters": { "insight": "trends", "events": [{ "order": 0, "id": "$pageview" }], "properties": [{ "key": "$geoip_country_name", "type": "person", "value": ["france"], "operator": "exact", } # properties superceded by FF breakdown ], }, }, { "name": "funnels whatever", "filters": { "insight": "funnels", "events": [{ "order": 0, "id": "$pageview_funnel" }, { "order": 1, "id": "$pageleave_funnel" }], "properties": [ { "key": "$geoip_country_name", "type": "person", "value": ["france"], "operator": "exact", } # properties superceded by FF breakdown ], }, }, ], # target metric insignificant since we're testing secondaries right now "filters": { "insight": "trends", "events": [{ "order": 0, "id": "whatever" }], }, }, ) id = creation_response.json()["id"] response = self.client.get( f"/api/projects/{self.team.id}/experiments/{id}/secondary_results?id=0" ) self.assertEqual(200, response.status_code) response_data = response.json() self.assertEqual(len(response_data["result"].items()), 2) self.assertEqual(response_data["result"]["control"], 3) self.assertEqual(response_data["result"]["test"], 1) response = self.client.get( f"/api/projects/{self.team.id}/experiments/{id}/secondary_results?id=1" ) self.assertEqual(200, response.status_code) response_data = response.json() self.assertEqual(len(response_data["result"].items()), 2) self.assertAlmostEqual(response_data["result"]["control"], 1) self.assertEqual(response_data["result"]["test"], round(1 / 3, 3))
def test_funnel_with_groups_global_filtering(self): self._create_groups() events_by_person = { "user_1": [ { "event": "user signed up", "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:5" } }, { "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": { "$group_0": "org:6" }, # second event belongs to different group, so shouldn't complete funnel }, ], "user_2": [ { "event": "user signed up", # event belongs to different group, so shouldn't enter funnel "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:6" }, }, { "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": { "$group_0": "org:5" }, # same group, but different person, so not in funnel }, ], } created_people = journeys_for(events_by_person, self.team) params = FunnelRequest( events=json.dumps([ EventPattern(id="user signed up", type="events", order=0), EventPattern(id="paid", type="events", order=1), ]), date_from="2020-01-01", date_to="2020-01-14", insight=INSIGHT_FUNNELS, properties=json.dumps([{ "key": "industry", "value": "finance", "type": "group", "group_type_index": 0 }]), ) result = get_funnel_ok(self.client, self.team.pk, params) assert result["user signed up"]["count"] == 1 assert result["paid"]["count"] == 0 actors = get_funnel_actors_ok( self.client, result["user signed up"]["converted_people_url"]) actor_ids = [str(val["id"]) for val in actors] assert actor_ids == sorted([str(created_people["user_1"].uuid)])
def test_funnel_with_groups_entity_filtering(self): self._create_groups() events_by_person = { "user_1": [ { "event": "user signed up", "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:5" } }, { "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": { "$group_0": "org:6" }, # different group, but doesn't matter since not aggregating by groups }, { "event": "user signed up", # event belongs to different group, so shouldn't enter funnel "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:6" }, }, { "event": "paid", "timestamp": datetime(2020, 1, 3, 14), "properties": { "$group_0": "org:6" }, # event belongs to different group, so shouldn't enter funnel }, ], } created_people = journeys_for(events_by_person, self.team) params = FunnelRequest( events=json.dumps([ EventPattern(id="user signed up", type="events", order=0, properties={"$group_0": "org:5"}), EventPattern(id="paid", type="events", order=1), ]), date_from="2020-01-01", date_to="2020-01-14", insight=INSIGHT_FUNNELS, ) result = get_funnel_ok(self.client, self.team.pk, params) assert result["user signed up"]["count"] == 1 assert result["paid"]["count"] == 1 assert result["paid"]["average_conversion_time"] == 86400 actors = get_funnel_actors_ok( self.client, result["user signed up"]["converted_people_url"]) actor_ids = [str(val["id"]) for val in actors] assert actor_ids == sorted([str(created_people["user_1"].uuid)])
def test_secondary_metric_results_for_multiple_variants(self): journeys_for( { # trend metric first "person1_2_trend": [ { "event": "$pageview_trend", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_2" }, }, ], "person1_1_trend": [ { "event": "$pageview_trend", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_1" }, }, ], "person2_1_trend": [ { "event": "$pageview_trend", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_1" }, }, ], "person2_trend": [ { "event": "$pageview_trend", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "control" }, }, ], "person3_trend": [ { "event": "$pageview_trend", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "control" }, }, ], "person4_trend": [ { "event": "$pageview_trend", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "control" }, }, ], # doesn't have feature set "person_out_of_control": [ { "event": "$pageview_trend", "timestamp": "2020-01-03", }, ], "person_out_of_end_date": [ { "event": "$pageview_trend", "timestamp": "2020-08-03", "properties": { "$feature/a-b-test": "control" }, }, ], # funnel metric second "person1_2": [ { "event": "$pageview", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_2" }, }, { "event": "$pageleave", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test_2" }, }, ], "person1_1": [ { "event": "$pageview", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_1" }, }, { "event": "$pageleave", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test_1" }, }, ], "person2_1": [ { "event": "$pageview", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_1" }, }, { "event": "$pageleave", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test_1" }, }, ], "person1": [ { "event": "$pageview", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test" }, }, { "event": "$pageleave", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test" }, }, ], "person2": [ { "event": "$pageview", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "control" } }, { "event": "$pageleave", "timestamp": "2020-01-05", "properties": { "$feature/a-b-test": "control" } }, ], "person3": [ { "event": "$pageview", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "control" } }, { "event": "$pageleave", "timestamp": "2020-01-05", "properties": { "$feature/a-b-test": "control" } }, ], # doesn't have feature set "person_out_of_control": [ { "event": "$pageview", "timestamp": "2020-01-03", }, { "event": "$pageleave", "timestamp": "2020-01-05", }, ], "person_out_of_end_date": [ { "event": "$pageview", "timestamp": "2020-08-03", "properties": { "$feature/a-b-test": "control" } }, { "event": "$pageleave", "timestamp": "2020-08-05", "properties": { "$feature/a-b-test": "control" } }, ], # non-converters with FF "person4": [ { "event": "$pageview", "timestamp": "2020-01-03", "properties": { "$feature/a-b-test": "test" }, }, ], "person5": [ { "event": "$pageview", "timestamp": "2020-01-04", "properties": { "$feature/a-b-test": "test" }, }, ], "person6_1": [ { "event": "$pageview", "timestamp": "2020-01-02", "properties": { "$feature/a-b-test": "test_1" }, }, ], }, self.team, ) ff_key = "a-b-test" # generates the FF which should result in the above events^ creation_response = self.client.post( f"/api/projects/{self.team.id}/experiments/", { "name": "Test Experiment", "description": "", "start_date": "2020-01-01T00:00", "end_date": "2020-01-06T00:00", "feature_flag_key": ff_key, "parameters": { "feature_flag_variants": [ { "key": "control", "name": "Control Group", "rollout_percentage": 25 }, { "key": "test_1", "name": "Test Variant 1", "rollout_percentage": 25 }, { "key": "test_2", "name": "Test Variant 2", "rollout_percentage": 25 }, { "key": "test", "name": "Test Variant 3", "rollout_percentage": 25 }, ], }, "secondary_metrics": [ { "name": "secondary metric", "filters": { "insight": "trends", "events": [{ "order": 0, "id": "$pageview_trend" }] }, }, { "name": "funnel metric", "filters": { "insight": "funnels", "events": [{ "order": 0, "id": "$pageview" }, { "order": 1, "id": "$pageleave" }], }, }, ], # target metric insignificant since we're testing secondaries right now "filters": { "insight": "trends", "events": [{ "order": 0, "id": "whatever" }], }, }, ) id = creation_response.json()["id"] response = self.client.get( f"/api/projects/{self.team.id}/experiments/{id}/secondary_results?id=0" ) self.assertEqual(200, response.status_code) response_data = response.json() # trend missing 'test' variant, so it's not in the results self.assertEqual(len(response_data["result"].items()), 3) self.assertEqual(response_data["result"]["control"], 3) self.assertEqual(response_data["result"]["test_1"], 2) self.assertEqual(response_data["result"]["test_2"], 1) response = self.client.get( f"/api/projects/{self.team.id}/experiments/{id}/secondary_results?id=1" ) self.assertEqual(200, response.status_code) response_data = response.json() # funnel not missing 'test' variant, so it's in the results self.assertEqual(len(response_data["result"].items()), 4) self.assertAlmostEqual(response_data["result"]["control"], 1) self.assertAlmostEqual(response_data["result"]["test"], round(1 / 3, 3)) self.assertAlmostEqual(response_data["result"]["test_1"], round(2 / 3, 3)) self.assertAlmostEqual(response_data["result"]["test_2"], 1)
def test_properties_correlation_endpoint_provides_people_drill_down_urls( self): """ Here we are setting up three users, two with a specified property but differing values, and one with this property absent. We expect to be able to use the correlation people drill down urls to retrieve the associated people for each. """ with freeze_time("2020-01-01"): self.client.force_login(self.user) update_or_create_person(distinct_ids=["Person 1"], team_id=self.team.pk, properties={"$browser": "1"}) update_or_create_person(distinct_ids=["Person 2"], team_id=self.team.pk, properties={"$browser": "1"}) events = { "Person 1": [ # Failure / $browser::1 { "event": "signup", "timestamp": datetime(2020, 1, 1) }, ], "Person 2": [ # Success / $browser::1 { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], "Person 3": [ # Success / $browser not set { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } journeys_for(events_by_person=events, team=self.team) odds = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", funnel_correlation_type=FunnelCorrelationType.PROPERTIES, funnel_correlation_names=json.dumps(["$browser"]), ), ) (browser_correlation, ) = [ correlation for correlation in odds["result"]["events"] if correlation["event"]["event"] == "$browser::1" ] (notset_correlation, ) = [ correlation for correlation in odds["result"]["events"] if correlation["event"]["event"] == "$browser::" ] assert get_people_for_correlation_ok( client=self.client, correlation=browser_correlation) == { "success": ["Person 2"], "failure": ["Person 1"], } assert get_people_for_correlation_ok( client=self.client, correlation=notset_correlation) == { "success": ["Person 3"], "failure": [], }
def test_events_with_properties_correlation_endpoint_provides_people_drill_down_urls( self): with freeze_time("2020-01-01"): self.client.force_login(self.user) events = { "Person 1": [ # Failure / watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "properties": { "$browser": "1" }, "timestamp": datetime(2020, 1, 2) }, ], "Person 2": [ # Success / watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "properties": { "$browser": "1" }, "timestamp": datetime(2020, 1, 2) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], "Person 3": [ # Success / watched. We need to have three event instances # for this test otherwise the endpoint doesn't return results { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "properties": { "$browser": "1" }, "timestamp": datetime(2020, 1, 2) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], "Person 4": [ # Success / didn't watch. Want to use this user to verify # that we don't pull in unrelated users erroneously { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } journeys_for(events_by_person=events, team=self.team) odds = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( funnel_correlation_type=FunnelCorrelationType. EVENT_WITH_PROPERTIES, funnel_correlation_event_names=json.dumps( ["watched video"]), events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) assert odds["result"]["events"][0]["event"][ "event"] == "watched video::$browser::1" watched_video_correlation = odds["result"]["events"][0] assert get_people_for_correlation_ok( client=self.client, correlation=watched_video_correlation) == { "success": ["Person 2", "Person 3"], "failure": ["Person 1"], }
def test_event_correlation_endpoint_picks_up_events_for_odds_ratios(self): with freeze_time("2020-01-01"): self.client.force_login(self.user) # Add in two people: # # Person 1 - a single signup event # Person 2 - a signup event and a view insights event # # Both of them have a "watched video" event # # We then create Person 3, one successful, the other # not. Both have not watched the video. # # So our contingency table for "watched video" should be # # | | success | failure | total | # | ---------------- | -------- | -------- | -------- | # | watched | 1 | 1 | 2 | # | did not watched | 1 | 0 | 1 | # | total | 2 | 1 | 3 | # # For Calculating Odds Ratio, we add a prior count of 1 to everything # # So our odds ratio should be # (success + prior / failure + prior) * (failure_total - failure + prior / success_total - success + prior) # = ( 1 + 1 / 1 + 1) * ( 1 - 1 + 1 / 2 - 1 + 1) # = 1 / 2 events = { "Person 1": [ # Failure / watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "timestamp": datetime(2020, 1, 2) }, ], "Person 2": [ # Success / watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "timestamp": datetime(2020, 1, 2) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], "Person 3": [ # Success / did not watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } journeys_for(events_by_person=events, team=self.team) odds = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) assert odds == { "is_cached": False, "last_refresh": "2020-01-01T00:00:00Z", "result": { "events": [ { "event": { "event": "watched video", "elements": [], "properties": {} }, "failure_count": 1, "success_count": 1, "success_people_url": ANY, "failure_people_url": ANY, "odds_ratio": 1 / 2, "correlation_type": "failure", }, ], "skewed": False, }, }
def test_funnel_breakdown_group(self): self._create_groups() people = journeys_for( { "person1": [ { "event": "sign up", "timestamp": datetime(2020, 1, 1, 12), "properties": { "$group_0": "org:5", "$browser": "Chrome" }, }, { "event": "play movie", "timestamp": datetime(2020, 1, 1, 13), "properties": { "$group_0": "org:5", "$browser": "Chrome" }, }, { "event": "buy", "timestamp": datetime(2020, 1, 1, 15), "properties": { "$group_0": "org:5", "$browser": "Chrome" }, }, ], "person2": [ { "event": "sign up", "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:6", "$browser": "Safari" }, }, { "event": "play movie", "timestamp": datetime(2020, 1, 2, 16), "properties": { "$group_0": "org:6", "$browser": "Safari" }, }, ], "person3": [ { "event": "sign up", "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:6", "$browser": "Safari" }, }, ], }, self.team, ) filters = { "events": [{ "id": "sign up", "order": 0 }, { "id": "play movie", "order": 1 }, { "id": "buy", "order": 2 }], "insight": INSIGHT_FUNNELS, "date_from": "2020-01-01", "date_to": "2020-01-08", "funnel_window_days": 7, "breakdown": "industry", "breakdown_type": "group", "breakdown_group_type_index": 0, } filter = Filter(data=filters, team=self.team) result = Funnel(filter, self.team).run() assert_funnel_breakdown_result_is_correct( result[0], [ FunnelStepResult( name="sign up", breakdown="finance", count=1), FunnelStepResult( name="play movie", breakdown="finance", count=1, average_conversion_time=3600.0, median_conversion_time=3600.0, ), FunnelStepResult( name="buy", breakdown="finance", count=1, average_conversion_time=7200.0, median_conversion_time=7200.0, ), ], ) # Querying persons when aggregating by persons should be ok, despite group breakdown self.assertCountEqual( self._get_actor_ids_at_step(filter, 1, "finance"), [people["person1"].uuid]) self.assertCountEqual( self._get_actor_ids_at_step(filter, 2, "finance"), [people["person1"].uuid]) assert_funnel_breakdown_result_is_correct( result[1], [ FunnelStepResult( name="sign up", breakdown="technology", count=2), FunnelStepResult( name="play movie", breakdown="technology", count=1, average_conversion_time=7200.0, median_conversion_time=7200.0, ), FunnelStepResult( name="buy", breakdown="technology", count=0), ], ) self.assertCountEqual( self._get_actor_ids_at_step(filter, 1, "technology"), [people["person2"].uuid, people["person3"].uuid]) self.assertCountEqual( self._get_actor_ids_at_step(filter, 2, "technology"), [people["person2"].uuid])
def test_event_correlation_endpoint_does_not_include_historical_events( self): with freeze_time("2020-01-01"): self.client.force_login(self.user) # Add in two people: # # Person 1 - a single signup event # Person 2 - a signup event and a view insights event # # Both of them have a "watched video" event but they are before the # signup event events = { "Person 1": [ { "event": "watched video", "timestamp": datetime(2019, 1, 2) }, { "event": "signup", "timestamp": datetime(2020, 1, 1) }, ], "Person 2": [ { "event": "watched video", "timestamp": datetime(2019, 1, 2) }, { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } journeys_for(events_by_person=events, team=self.team) # We need to make sure we clear the cache other tests that have run # done interfere with this test cache.clear() odds = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) assert odds == { "is_cached": False, "last_refresh": "2020-01-01T00:00:00Z", "result": { "events": [], "skewed": False }, }
def test_event_correlation_is_partitioned_by_team(self): """ Ensure there's no crosstalk between teams We check this by: 1. loading events into team 1 2. checking correlation for team 1 3. loading events into team 2 4. checking correlation for team 1 again, they should be the same """ with freeze_time("2020-01-01"): self.client.force_login(self.user) events = { "Person 1": [ { "event": "watched video", "timestamp": datetime(2019, 1, 2) }, { "event": "signup", "timestamp": datetime(2020, 1, 1) }, ], "Person 2": [ { "event": "watched video", "timestamp": datetime(2019, 1, 2) }, { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } journeys_for(events_by_person=events, team=self.team) odds_before = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) other_team = create_team(organization=self.organization) journeys_for(events_by_person=events, team=other_team) # We need to make sure we clear the cache so we get the same results again cache.clear() odds_after = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) assert odds_before == odds_after
def test_events_correlation_endpoint_provides_people_drill_down_urls(self): """ Here we are setting up three users, and looking to retrieve one correlation for watched video, with a url we can use to retrieve people that successfully completed the funnel AND watched the video, and another for people that did not complete the funnel but also watched the video. """ with freeze_time("2020-01-01"): self.client.force_login(self.user) events = { "Person 1": [ # Failure / watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "timestamp": datetime(2020, 1, 2) }, ], "Person 2": [ # Success / watched { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "watched video", "timestamp": datetime(2020, 1, 2) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], "Person 3": [ # Success / did not watched. We don't expect to retrieve # this one as part of the { "event": "signup", "timestamp": datetime(2020, 1, 1) }, { "event": "view insights", "timestamp": datetime(2020, 1, 3) }, ], } journeys_for(events_by_person=events, team=self.team) odds = get_funnel_correlation_ok( client=self.client, team_id=self.team.pk, request=FunnelCorrelationRequest( events=json.dumps([ EventPattern(id="signup"), EventPattern(id="view insights") ]), date_to="2020-04-04", ), ) assert odds["result"]["events"][0]["event"][ "event"] == "watched video" watched_video_correlation = odds["result"]["events"][0] assert get_people_for_correlation_ok( client=self.client, correlation=watched_video_correlation) == { "success": ["Person 2"], "failure": ["Person 1"], }
def test_funnel_aggregate_by_groups_breakdown_group(self): self._create_groups() journeys_for( { "person1": [ { "event": "sign up", "timestamp": datetime(2020, 1, 1, 12), "properties": { "$group_0": "org:5", "$browser": "Chrome" }, }, { "event": "play movie", "timestamp": datetime(2020, 1, 1, 13), "properties": { "$group_0": "org:5", "$browser": "Chrome" }, }, { "event": "buy", "timestamp": datetime(2020, 1, 1, 15), "properties": { "$group_0": "org:5", "$browser": "Chrome" }, }, ], "person2": [ { "event": "sign up", "timestamp": datetime(2020, 1, 2, 14), "properties": { "$group_0": "org:6", "$browser": "Safari" }, }, { "event": "play movie", "timestamp": datetime(2020, 1, 2, 16), "properties": { "$group_0": "org:6", "$browser": "Safari" }, }, ], "person3": [ { "event": "buy", "timestamp": datetime(2020, 1, 2, 18), "properties": { "$group_0": "org:6", "$browser": "Safari" }, }, ], }, self.team, ) filters = { "events": [{ "id": "sign up", "order": 0 }, { "id": "play movie", "order": 1 }, { "id": "buy", "order": 2 }], "insight": INSIGHT_FUNNELS, "date_from": "2020-01-01", "date_to": "2020-01-08", "funnel_window_days": 7, "breakdown": "industry", "breakdown_type": "group", "breakdown_group_type_index": 0, "aggregation_group_type_index": 0, } result = Funnel(Filter(data=filters, team=self.team), self.team).run() assert_funnel_breakdown_result_is_correct( result[0], [ FunnelStepResult( name="sign up", breakdown="finance", count=1), FunnelStepResult( name="play movie", breakdown="finance", count=1, average_conversion_time=3600.0, median_conversion_time=3600.0, ), FunnelStepResult( name="buy", breakdown="finance", count=1, average_conversion_time=7200.0, median_conversion_time=7200.0, ), ], ) assert_funnel_breakdown_result_is_correct( result[1], [ FunnelStepResult( name="sign up", breakdown="technology", count=1), FunnelStepResult( name="play movie", breakdown="technology", count=1, average_conversion_time=7200.0, median_conversion_time=7200.0, ), FunnelStepResult( name="buy", breakdown="technology", count=1, average_conversion_time=7200.0, median_conversion_time=7200.0, ), ], )