def test_person_query_with_extra_requested_fields(testdata, team, snapshot): filter = Filter(data={ "properties": [ { "key": "email", "type": "person", "value": "posthog", "operator": "icontains" }, ], "breakdown": "person_prop_4326", "breakdown_type": "person", }, ) assert person_query(team, filter) == snapshot assert run_query(team, filter) == {"rows": 2, "columns": 2} filter = filter.with_data({ "breakdown": "email", "breakdown_type": "person" }) assert person_query(team, filter) == snapshot assert run_query(team, filter) == {"rows": 2, "columns": 2}
def _serialize_lifecycle(self, entity: Entity, filter: Filter, team_id: int) -> List[Dict[str, Any]]: period = filter.interval or "day" num_intervals, prev_date_from, date_from, date_to, after_date_to = get_time_diff( period, filter.date_from, filter.date_to, team_id) interval_trunc, sub_interval = get_trunc_func(period=period) # include the before and after when filteirng all events filter = filter.with_data({ "date_from": prev_date_from.isoformat(), "date_to": after_date_to.isoformat() }) filtered_events = (Event.objects.filter( team_id=team_id).add_person_id(team_id).filter( filter_events(team_id, filter, entity))) event_query, event_params = queryset_to_named_query( filtered_events, "events") earliest_events_filtered = (Event.objects.filter( team_id=team_id).add_person_id(team_id).filter( filter_events(team_id, filter, entity, include_dates=False))) earliest_events_query, earliest_events_params = queryset_to_named_query( earliest_events_filtered, "earliest_events") with connection.cursor() as cursor: cursor.execute( LIFECYCLE_SQL.format( action_join=ACTION_JOIN if entity.type == TREND_FILTER_TYPE_ACTIONS else "", event_condition="{} = %(event)s".format( "action_id" if entity.type == TREND_FILTER_TYPE_ACTIONS else "event"), events=event_query, earliest_events=earliest_events_query, ), { "team_id": team_id, "event": entity.id, "interval": interval_trunc, "one_interval": "1 " + interval_trunc, "sub_interval": "1 " + sub_interval, "num_intervals": num_intervals, "prev_date_from": prev_date_from, "date_from": date_from, "date_to": date_to, "after_date_to": after_date_to, **event_params, **earliest_events_params, }, ) res = [] for val in cursor.fetchall(): label = "{} - {}".format(entity.name, val[2]) additional_values = {"label": label, "status": val[2]} parsed_result = parse_response(val, filter, additional_values) res.append(parsed_result) return res
def test_correlation_with_properties_raises_validation_error(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": "properties", # "funnel_correlation_names": ["$browser"], missing value } filter = Filter(data=filters) correlation = FunnelCorrelation(filter, self.team) _create_person(distinct_ids=[f"user_1"], team_id=self.team.pk, properties={"$browser": "Positive"}) _create_event( team=self.team, event="user signed up", distinct_id=f"user_1", timestamp="2020-01-02T14:00:00Z", ) _create_event( team=self.team, event="rick", distinct_id=f"user_1", timestamp="2020-01-03T14:00:00Z", ) _create_event( team=self.team, event="paid", distinct_id=f"user_1", timestamp="2020-01-04T14:00:00Z", ) with self.assertRaises(ValidationError): correlation._run() filter = filter.with_data( {"funnel_correlation_type": "event_with_properties"}) # missing "funnel_correlation_event_names": ["rick"], with self.assertRaises(ValidationError): FunnelCorrelation(filter, self.team)._run()
def test_element(self): _create_event( event="$autocapture", team=self.team, distinct_id="whatever", properties={"attr": "some_other_val"}, elements=[ Element( tag_name="a", href="/a-url", attr_class=["small"], text="bla bla", attributes={}, nth_child=1, nth_of_type=0, ), Element(tag_name="button", attr_class=["btn", "btn-primary"], nth_child=0, nth_of_type=0), Element(tag_name="div", nth_child=0, nth_of_type=0), Element(tag_name="label", nth_child=0, nth_of_type=0, attr_id="nested",), ], ) _create_event( event="$pageview", team=self.team, distinct_id="whatever", properties={"attr": "some_val"}, elements=[ Element( tag_name="a", href="/a-url", attr_class=["small"], text="bla bla", attributes={}, nth_child=1, nth_of_type=0, ), Element(tag_name="button", attr_class=["btn", "btn-secondary"], nth_child=0, nth_of_type=0), Element(tag_name="div", nth_child=0, nth_of_type=0), Element(tag_name="img", nth_child=0, nth_of_type=0, attr_id="nested",), ], ) filter = Filter( data={ "events": [{"id": "event_name", "order": 0},], "properties": [{"key": "tag_name", "value": ["label"], "operator": "exact", "type": "element"}], } ) self._run_query(filter) self._run_query( filter.with_data( {"properties": [{"key": "tag_name", "value": [], "operator": "exact", "type": "element"}],} ) )
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_funnel_exclusions_invalid_params(self): filters = { "events": [ { "id": "user signed up", "type": "events", "order": 0 }, { "id": "paid", "type": "events", "order": 1 }, { "id": "blah", "type": "events", "order": 2 }, ], "insight": INSIGHT_FUNNELS, "funnel_window_days": 14, "exclusions": [ { "id": "x", "type": "events", "funnel_from_step": 1, "funnel_to_step": 1 }, ], } filter = Filter(data=filters) self.assertRaises( ValidationError, lambda: ClickhouseFunnelUnordered(filter, self.team).run()) # partial windows not allowed for unordered filter = filter.with_data({ "exclusions": [{ "id": "x", "type": "events", "funnel_from_step": 0, "funnel_to_step": 1 }] }) self.assertRaises( ValidationError, lambda: ClickhouseFunnelUnordered(filter, self.team).run())
def _get_people_for_event(self, filter: Filter, event_name: str, properties=None, success=True): person_filter = filter.with_data({ "funnel_correlation_person_entity": { "id": event_name, "type": "events", "properties": properties }, "funnel_correlation_person_converted": "TrUe" if success else "falSE", }) results, _ = FunnelCorrelationPersons(person_filter, self.team).run() return [row["uuid"] for row in results]
def _handle_date_interval(filter: Filter) -> Filter: # adhoc date handling. parsed differently with django orm date_from = filter.date_from or timezone.now() data: Dict = {} if filter.interval == "month": data.update({ "date_to": (date_from + relativedelta(months=1) - timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S") }) elif filter.interval == "week": data.update({ "date_to": (date_from + relativedelta(weeks=1) - timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S") }) elif filter.interval == "day": data.update({"date_to": date_from}) elif filter.interval == "hour": data.update({"date_to": date_from + timedelta(hours=1)}) return filter.with_data(data)
def test_invalid_steps(self): data = { "insight": INSIGHT_FUNNELS, "interval": "day", "date_from": "2021-05-01 00:00:00", "date_to": "2021-05-07 00:00:00", "funnel_window_days": 7, "funnel_step": "blah", "events": [ { "id": "step one", "order": 0 }, { "id": "step two", "order": 1 }, { "id": "step three", "order": 2 }, ], } filter = Filter(data=data) with self.assertRaises(ValueError): ClickhouseFunnelUnorderedPersons(filter, self.team).run() filter = filter.with_data({"funnel_step": -1}) results, _ = ClickhouseFunnelUnorderedPersons(filter, self.team).run() self.assertEqual(0, len(results))
def get_people( self, filter: Filter, team_id: int, target_date: datetime, lifecycle_type: str, request: Request, limit: int = 100, ): entity = filter.entities[0] period = filter.interval or "day" num_intervals, prev_date_from, date_from, date_to, after_date_to = get_time_diff( period, filter.date_from, filter.date_to, team_id ) interval_trunc, sub_interval = get_trunc_func(period=period) # include the before and after when filteirng all events filter = filter.with_data({"date_from": prev_date_from.isoformat(), "date_to": after_date_to.isoformat()}) filtered_events = ( Event.objects.filter(team_id=team_id).add_person_id(team_id).filter(filter_events(team_id, filter, entity)) ) event_query, event_params = queryset_to_named_query(filtered_events) earliest_events_filtered = ( Event.objects.filter(team_id=team_id) .add_person_id(team_id) .filter(filter_events(team_id, filter, entity, include_dates=False)) ) earliest_events_query, earliest_events_params = queryset_to_named_query( earliest_events_filtered, "earliest_events" ) with connection.cursor() as cursor: cursor.execute( LIFECYCLE_PEOPLE_SQL.format( action_join=ACTION_JOIN if entity.type == TREND_FILTER_TYPE_ACTIONS else "", event_condition="{} = %(event)s".format( "action_id" if entity.type == TREND_FILTER_TYPE_ACTIONS else "event" ), events=event_query, earliest_events=earliest_events_query, ), { "team_id": team_id, "event": entity.id, "interval": interval_trunc, "one_interval": "1 " + interval_trunc, "sub_interval": "1 " + sub_interval, "num_intervals": num_intervals, "prev_date_from": prev_date_from, "date_from": date_from, "date_to": date_to, "after_date_to": after_date_to, "target_date": target_date, "status": lifecycle_type, "offset": filter.offset, "limit": limit, **event_params, **earliest_events_params, }, ) pids = cursor.fetchall() people = Person.objects.filter(team_id=team_id, id__in=[p[0] for p in pids],) from posthog.api.person import PersonSerializer people = filter_persons(team_id, request, people) # type: ignore people = people.prefetch_related(Prefetch("persondistinctid_set", to_attr="distinct_ids_cache")) return PersonSerializer(people, many=True).data
"value": ["value"], "operator": "exact", "type": "group", "group_type_index": 2 }, ] BASE_FILTER = Filter( {"events": [{ "id": "$pageview", "type": "events", "order": 0 }]}) FILTER_WITH_GROUPS = BASE_FILTER.with_data( {"properties": { "type": "AND", "values": PROPERTIES_OF_ALL_TYPES }}) TEAM_ID = 3 class TestPersonPropertySelector(unittest.TestCase): def test_basic_selector(self): filter = BASE_FILTER.with_data({ "properties": { "type": "OR", "values": [ { "key": "person_prop",
{ "key": "group_prop", "value": ["value"], "operator": "exact", "type": "group", "group_type_index": 2 }, ] BASE_FILTER = Filter( {"events": [{ "id": "$pageview", "type": "events", "order": 0 }]}) FILTER_WITH_PROPERTIES = BASE_FILTER.with_data( {"properties": PROPERTIES_OF_ALL_TYPES}) class TestColumnOptimizer(ClickhouseTestMixin, APIBaseTest): def setUp(self): super().setUp() self.team.test_account_filters = PROPERTIES_OF_ALL_TYPES self.team.save() def test_properties_used_in_filter(self): properties_used_in_filter = lambda filter: ColumnOptimizer( filter, self.team.id).properties_used_in_filter self.assertEqual(properties_used_in_filter(BASE_FILTER), {}) self.assertEqual( properties_used_in_filter(FILTER_WITH_PROPERTIES),
def test_funnel_times_with_different_conversion_windows(self): filters = { "events": [ {"id": "user signed up", "type": "events", "order": 0}, {"id": "pageview", "type": "events", "order": 1}, ], "insight": INSIGHT_FUNNELS, "funnel_window_interval": 14, "funnel_window_interval_unit": "day", "date_from": "2020-01-01", "date_to": "2020-01-14", } filter = Filter(data=filters) funnel = Funnel(filter, self.team) # event people = journeys_for( { "stopped_after_signup1": [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14)}, {"event": "pageview", "timestamp": datetime(2020, 1, 2, 14, 5)}, ], "stopped_after_signup2": [{"event": "user signed up", "timestamp": datetime(2020, 1, 2, 14, 3)},], "stopped_after_signup3": [ {"event": "user signed up", "timestamp": datetime(2020, 1, 2, 12)}, {"event": "pageview", "timestamp": datetime(2020, 1, 2, 12, 15)}, ], }, self.team, ) result = funnel.run() self.assertEqual(result[0]["name"], "user signed up") self.assertEqual(result[0]["count"], 3) self.assertEqual(result[1]["count"], 2) self.assertEqual(result[1]["average_conversion_time"], 600) self.assertCountEqual( self._get_actor_ids_at_step(filter, 1), [ people["stopped_after_signup1"].uuid, people["stopped_after_signup2"].uuid, people["stopped_after_signup3"].uuid, ], ) self.assertCountEqual( self._get_actor_ids_at_step(filter, 2), [people["stopped_after_signup1"].uuid, people["stopped_after_signup3"].uuid], ) filter = filter.with_data({"funnel_window_interval": 5, "funnel_window_interval_unit": "minute"}) funnel = Funnel(filter, self.team) result4 = funnel.run() self.assertNotEqual(result, result4) self.assertEqual(result4[0]["name"], "user signed up") self.assertEqual(result4[0]["count"], 3) self.assertEqual(result4[1]["count"], 1) self.assertEqual(result4[1]["average_conversion_time"], 300) self.assertCountEqual( self._get_actor_ids_at_step(filter, 1), [ people["stopped_after_signup1"].uuid, people["stopped_after_signup2"].uuid, people["stopped_after_signup3"].uuid, ], ) self.assertCountEqual( self._get_actor_ids_at_step(filter, 2), [people["stopped_after_signup1"].uuid], )
def test_funnel_times_with_different_conversion_windows(self): filters = { "events": [ { "id": "user signed up", "type": "events", "order": 0 }, { "id": "pageview", "type": "events", "order": 1 }, ], "insight": INSIGHT_FUNNELS, "funnel_window_interval": 14, "funnel_window_interval_unit": "day", "date_from": "2020-01-01", "date_to": "2020-01-14", } filter = Filter(data=filters) funnel = Funnel(filter, self.team) # event person1_stopped_after_two_signups = _create_person( distinct_ids=["stopped_after_signup1"], team_id=self.team.pk) _create_event( team=self.team, event="user signed up", distinct_id="stopped_after_signup1", timestamp="2020-01-02T14:00:00Z", ) _create_event(team=self.team, event="pageview", distinct_id="stopped_after_signup1", timestamp="2020-01-02T14:05:00Z") person2_stopped_after_signup = _create_person( distinct_ids=["stopped_after_signup2"], team_id=self.team.pk) _create_event( team=self.team, event="user signed up", distinct_id="stopped_after_signup2", timestamp="2020-01-02T14:03:00Z", ) person3_stopped_after_two_signups = _create_person( distinct_ids=["stopped_after_signup3"], team_id=self.team.pk) _create_event( team=self.team, event="user signed up", distinct_id="stopped_after_signup3", timestamp="2020-01-02T12:00:00Z", ) _create_event(team=self.team, event="pageview", distinct_id="stopped_after_signup3", timestamp="2020-01-02T12:15:00Z") result = funnel.run() self.assertEqual(result[0]["name"], "user signed up") self.assertEqual(result[0]["count"], 3) self.assertEqual(result[1]["count"], 2) self.assertEqual(result[1]["average_conversion_time"], 600) self.assertCountEqual( self._get_people_at_step(filter, 1), [ person1_stopped_after_two_signups.uuid, person2_stopped_after_signup.uuid, person3_stopped_after_two_signups.uuid, ], ) self.assertCountEqual( self._get_people_at_step(filter, 2), [ person1_stopped_after_two_signups.uuid, person3_stopped_after_two_signups.uuid ], ) filter = filter.with_data({ "funnel_window_interval": 5, "funnel_window_interval_unit": "minute" }) funnel = Funnel(filter, self.team) result4 = funnel.run() self.assertNotEqual(result, result4) self.assertEqual(result4[0]["name"], "user signed up") self.assertEqual(result4[0]["count"], 3) self.assertEqual(result4[1]["count"], 1) self.assertEqual(result4[1]["average_conversion_time"], 300) self.assertCountEqual( self._get_people_at_step(filter, 1), [ person1_stopped_after_two_signups.uuid, person2_stopped_after_signup.uuid, person3_stopped_after_two_signups.uuid, ], ) self.assertCountEqual( self._get_people_at_step(filter, 2), [person1_stopped_after_two_signups.uuid], )
def test_correlation_with_multiple_properties(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": "properties", "funnel_correlation_names": ["$browser", "$nice"], } filter = Filter(data=filters) correlation = FunnelCorrelation(filter, self.team) # 5 successful people with both properties for i in range(5): _create_person(distinct_ids=[f"user_{i}"], team_id=self.team.pk, properties={ "$browser": "Positive", "$nice": "very" }) _create_event( team=self.team, event="user signed up", distinct_id=f"user_{i}", timestamp="2020-01-02T14:00:00Z", ) _create_event( team=self.team, event="paid", distinct_id=f"user_{i}", timestamp="2020-01-04T14:00:00Z", ) # 10 successful people with some different properties for i in range(5, 15): _create_person(distinct_ids=[f"user_{i}"], team_id=self.team.pk, properties={ "$browser": "Positive", "$nice": "not" }) _create_event( team=self.team, event="user signed up", distinct_id=f"user_{i}", timestamp="2020-01-02T14:00:00Z", ) _create_event( team=self.team, event="paid", distinct_id=f"user_{i}", timestamp="2020-01-04T14:00:00Z", ) # 5 Unsuccessful people with some common properties for i in range(15, 20): _create_person(distinct_ids=[f"user_{i}"], team_id=self.team.pk, properties={ "$browser": "Negative", "$nice": "smh" }) _create_event( team=self.team, event="user signed up", distinct_id=f"user_{i}", timestamp="2020-01-02T14:00:00Z", ) # One Positive with failure, no $nice property _create_person(distinct_ids=[f"user_fail"], team_id=self.team.pk, properties={"$browser": "Positive"}) _create_event( team=self.team, event="user signed up", distinct_id=f"user_fail", timestamp="2020-01-02T14:00:00Z", ) # One Negative with success, no $nice property _create_person(distinct_ids=[f"user_succ"], team_id=self.team.pk, properties={"$browser": "Negative"}) _create_event( team=self.team, event="user signed up", distinct_id=f"user_succ", timestamp="2020-01-02T14:00:00Z", ) _create_event( team=self.team, event="paid", distinct_id=f"user_succ", timestamp="2020-01-04T14:00:00Z", ) result = correlation._run()[0] # Success Total = 5 + 10 + 1 = 16 # Failure Total = 5 + 1 = 6 # Add 1 for priors odds_ratios = [item.pop("odds_ratio") for item in result] # type: ignore expected_odds_ratios = [ (16 / 2) * ((7 - 1) / (17 - 15)), (11 / 1) * ((7 - 0) / (17 - 10)), (6 / 1) * ((7 - 0) / (17 - 5)), (1 / 6) * ((7 - 5) / (17 - 0)), (2 / 6) * ((7 - 5) / (17 - 1)), (2 / 2) * ((7 - 1) / (17 - 1)), ] # (success + 1) / (failure + 1) for odds, expected_odds in zip(odds_ratios, expected_odds_ratios): self.assertAlmostEqual(odds, expected_odds) expected_result = [ { "event": "$browser::Positive", "success_count": 15, "failure_count": 1, # "odds_ratio": 24, "correlation_type": "success", }, { "event": "$nice::not", "success_count": 10, "failure_count": 0, # "odds_ratio": 11, "correlation_type": "success", }, { "event": "$nice::very", "success_count": 5, "failure_count": 0, # "odds_ratio": 3.5, "correlation_type": "success", }, { "event": "$nice::smh", "success_count": 0, "failure_count": 5, # "odds_ratio": 0.0196078431372549, "correlation_type": "failure", }, { "event": "$browser::Negative", "success_count": 1, "failure_count": 5, # "odds_ratio": 0.041666666666666664, "correlation_type": "failure", }, { "event": "$nice::", "success_count": 1, "failure_count": 1, # "odds_ratio": 0.375, "correlation_type": "failure", }, ] self.assertEqual(result, expected_result) # _run property correlation with filter on all properties filter = filter.with_data({"funnel_correlation_names": ["$all"]}) correlation = FunnelCorrelation(filter, self.team) new_result = correlation._run()[0] odds_ratios = [item.pop("odds_ratio") for item in new_result] # type: ignore new_expected_odds_ratios = expected_odds_ratios[:-1] new_expected_result = expected_result[:-1] # When querying all properties, we don't consider properties that don't exist for part of the data # since users aren't explicitly asking for that property. Thus, # We discard $nice:: because it's an empty result set for odds, expected_odds in zip(odds_ratios, new_expected_odds_ratios): self.assertAlmostEqual(odds, expected_odds) self.assertEqual(new_result, new_expected_result) filter = filter.with_data( {"funnel_correlation_exclude_names": ["$browser"]}) # search for $all but exclude $browser correlation = FunnelCorrelation(filter, self.team) new_result = correlation._run()[0] odds_ratios = [item.pop("odds_ratio") for item in new_result] # type: ignore new_expected_odds_ratios = expected_odds_ratios[ 1:4] # choosing the $nice property values new_expected_result = expected_result[1:4] for odds, expected_odds in zip(odds_ratios, new_expected_odds_ratios): self.assertAlmostEqual(odds, expected_odds) self.assertEqual(new_result, new_expected_result)
def test_basic_funnel_correlation_with_events(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) correlation = FunnelCorrelation(filter, self.team) for i in range(10): _create_person(distinct_ids=[f"user_{i}"], team_id=self.team.pk) _create_event( team=self.team, event="user signed up", distinct_id=f"user_{i}", timestamp="2020-01-02T14:00:00Z", ) if i % 2 == 0: _create_event( team=self.team, event="positively_related", distinct_id=f"user_{i}", timestamp="2020-01-03T14:00:00Z", ) _create_event( team=self.team, event="paid", distinct_id=f"user_{i}", timestamp="2020-01-04T14:00:00Z", ) for i in range(10, 20): _create_person(distinct_ids=[f"user_{i}"], team_id=self.team.pk) _create_event( team=self.team, event="user signed up", distinct_id=f"user_{i}", timestamp="2020-01-02T14:00:00Z", ) if i % 2 == 0: _create_event( team=self.team, event="negatively_related", distinct_id=f"user_{i}", timestamp="2020-01-03T14:00:00Z", ) result = correlation._run()[0] odds_ratios = [item.pop("odds_ratio") for item in result] # type: ignore expected_odds_ratios = [11, 1 / 11] for odds, expected_odds in zip(odds_ratios, expected_odds_ratios): self.assertAlmostEqual(odds, expected_odds) self.assertEqual( result, [ { "event": "positively_related", "success_count": 5, "failure_count": 0, # "odds_ratio": 11.0, "correlation_type": "success", }, { "event": "negatively_related", "success_count": 0, "failure_count": 5, # "odds_ratio": 1 / 11, "correlation_type": "failure", }, ], ) self.assertEqual( len(self._get_people_for_event(filter, "positively_related")), 5) self.assertEqual( len( self._get_people_for_event(filter, "positively_related", success=False)), 0) self.assertEqual( len( self._get_people_for_event(filter, "negatively_related", success=False)), 5) self.assertEqual( len(self._get_people_for_event(filter, "negatively_related")), 0) # Now exclude positively_related filter = filter.with_data( {"funnel_correlation_exclude_event_names": ["positively_related"]}) correlation = FunnelCorrelation(filter, self.team) result = correlation._run()[0] odds_ratio = result[0].pop("odds_ratio") # type: ignore expected_odds_ratio = 1 / 11 self.assertAlmostEqual(odds_ratio, expected_odds_ratio) self.assertEqual( result, [ { "event": "negatively_related", "success_count": 0, "failure_count": 5, # "odds_ratio": 1 / 11, "correlation_type": "failure", }, ], ) # Getting specific people isn't affected by exclude_events self.assertEqual( len(self._get_people_for_event(filter, "positively_related")), 5) self.assertEqual( len( self._get_people_for_event(filter, "positively_related", success=False)), 0) self.assertEqual( len( self._get_people_for_event(filter, "negatively_related", success=False)), 5) self.assertEqual( len(self._get_people_for_event(filter, "negatively_related")), 0)