Example #1
0
    def test_first_step_breakdowns_with_multi_property_breakdown(self):
        person1, person2 = self._create_browser_breakdown_events()
        filter = Filter(
            data={
                "insight": INSIGHT_FUNNELS,
                "date_from": "2020-01-01",
                "date_to": "2020-01-08",
                "interval": "day",
                "funnel_window_days": 7,
                "funnel_step": 1,
                "events": [{"id": "sign up", "order": 0}, {"id": "play movie", "order": 1}, {"id": "buy", "order": 2}],
                "breakdown_type": "event",
                "breakdown": ["$browser", "$browser_version"],
            }
        )
        _, results = ClickhouseFunnelActors(filter, self.team).get_actors()

        self.assertCountEqual([val["id"] for val in results], [person1.uuid, person2.uuid])

        _, results = ClickhouseFunnelActors(
            filter.with_data({"funnel_step_breakdown": "Chrome"}), self.team
        ).get_actors()

        self.assertCountEqual([val["id"] for val in results], [person1.uuid])

        _, results = ClickhouseFunnelActors(
            filter.with_data({"funnel_step_breakdown": "Safari"}), self.team
        ).get_actors()
        self.assertCountEqual([val["id"] for val in results], [person2.uuid])
Example #2
0
    def test_steps_with_custom_steps_parameter_are_equivalent_to_funnel_step(self):
        self._create_sample_data_multiple_dropoffs()
        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,
            "events": [
                {"id": "step one", "order": 0},
                {"id": "step two", "order": 1},
                {"id": "step three", "order": 2},
            ],
        }
        base_filter = Filter(data=data)

        parameters = [
            #  funnel_step,  custom_steps, expected_results
            (1, [1, 2, 3], 35),
            (2, [2, 3], 15),
            (3, [3], 5),
            (-2, [1], 20),
            (-3, [2], 10),
        ]

        for funnel_step, custom_steps, expected_count in parameters:
            filter = base_filter.with_data({"funnel_step": funnel_step})
            _, results = ClickhouseFunnelActors(filter, self.team).get_actors()

            new_filter = base_filter.with_data({"funnel_custom_steps": custom_steps})
            _, new_results = ClickhouseFunnelActors(new_filter, self.team).get_actors()

            self.assertEqual(new_results, results)
            self.assertEqual(len(results), expected_count)
Example #3
0
    def test_first_step_breakdowns(self):
        person1, person2 = self._create_browser_breakdown_events()
        filter = Filter(
            data={
                "insight":
                INSIGHT_FUNNELS,
                "date_from":
                "2020-01-01",
                "date_to":
                "2020-01-08",
                "interval":
                "day",
                "funnel_window_days":
                7,
                "funnel_step":
                1,
                "events": [
                    {
                        "id": "sign up",
                        "order": 0
                    },
                    {
                        "id": "play movie",
                        "order": 1
                    },
                    {
                        "id": "buy",
                        "order": 2
                    },
                ],
                "breakdown_type":
                "event",
                "breakdown":
                "$browser",
            })
        results = ClickhouseFunnelPersons(filter, self.team)._exec_query()

        self.assertCountEqual([val[0] for val in results],
                              [person1.uuid, person2.uuid])

        results = ClickhouseFunnelPersons(
            filter.with_data({"funnel_step_breakdown": "Chrome"}),
            self.team)._exec_query()

        self.assertCountEqual([val[0] for val in results], [person1.uuid])

        results = ClickhouseFunnelPersons(
            filter.with_data({"funnel_step_breakdown": "Safari"}),
            self.team)._exec_query()
        self.assertCountEqual([val[0] for val in results], [person2.uuid])

        results = ClickhouseFunnelPersons(
            filter.with_data({"funnel_step_breakdown": "Safari, Chrome"}),
            self.team)._exec_query()
        self.assertCountEqual([val[0] for val in results],
                              [person2.uuid, person1.uuid])
Example #4
0
    def calculate_funnel_correlation_persons(
        self, request: request.Request
    ) -> Dict[str, Tuple[list, Optional[str], Optional[str]]]:
        if request.user.is_anonymous or not self.team:
            return {"result": ([], None, None)}

        filter = Filter(request=request, data={"insight": INSIGHT_FUNNELS}, team=self.team)
        if not filter.correlation_person_limit:
            filter = filter.with_data({FUNNEL_CORRELATION_PERSON_LIMIT: 100})
        base_uri = request.build_absolute_uri("/")
        actors, serialized_actors = FunnelCorrelationActors(
            filter=filter, team=self.team, base_uri=base_uri
        ).get_actors()
        _should_paginate = should_paginate(actors, filter.correlation_person_limit)

        next_url = (
            format_query_params_absolute_url(
                request,
                filter.correlation_person_offset + filter.correlation_person_limit,
                offset_alias=FUNNEL_CORRELATION_PERSON_OFFSET,
                limit_alias=FUNNEL_CORRELATION_PERSON_LIMIT,
            )
            if _should_paginate
            else None
        )
        initial_url = format_query_params_absolute_url(request, 0)

        # cached_function expects a dict with the key result
        return {"result": (serialized_actors, next_url, initial_url)}
Example #5
0
    def test_steps_with_custom_steps_parameter_where_funnel_step_equivalence_isnt_possible(self):
        self._create_sample_data_multiple_dropoffs()
        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,
            "events": [
                {"id": "step one", "order": 0},
                {"id": "step two", "order": 1},
                {"id": "step three", "order": 2},
            ],
        }
        base_filter = Filter(data=data)

        parameters = [
            # custom_steps, expected_results
            ([1, 2], 30),
            ([1, 3], 25),
            ([3, 1], 25),
            ([1, 3, 3, 1], 25),
        ]

        for custom_steps, expected_count in parameters:
            new_filter = base_filter.with_data({"funnel_custom_steps": custom_steps})
            _, new_results = ClickhouseFunnelActors(new_filter, self.team).get_actors()

            self.assertEqual(len(new_results), expected_count)
Example #6
0
    def test_first_step_breakdown_person(self):
        person1, person2 = self._create_browser_breakdown_events()
        filter = Filter(
            data={
                "insight": INSIGHT_FUNNELS,
                "date_from": "2020-01-01",
                "date_to": "2020-01-08",
                "interval": "day",
                "funnel_window_days": 7,
                "funnel_step": 1,
                "events": [{"id": "sign up", "order": 0}, {"id": "play movie", "order": 1}, {"id": "buy", "order": 2}],
                "breakdown_type": "person",
                "breakdown": "$country",
            }
        )

        _, results = ClickhouseFunnelActors(filter, self.team).get_actors()
        self.assertCountEqual([val["id"] for val in results], [person1.uuid, person2.uuid])

        _, results = ClickhouseFunnelActors(filter.with_data({"funnel_step_breakdown": "EE"}), self.team).get_actors()
        self.assertCountEqual([val["id"] for val in results], [person2.uuid])

        # Check custom_steps give same answers for breakdowns
        _, custom_step_results = ClickhouseFunnelActors(
            filter.with_data({"funnel_step_breakdown": "EE", "funnel_custom_steps": [1, 2, 3]}), self.team
        ).get_actors()
        self.assertEqual(results, custom_step_results)

        _, results = ClickhouseFunnelActors(filter.with_data({"funnel_step_breakdown": "PL"}), self.team).get_actors()
        self.assertCountEqual([val["id"] for val in results], [person1.uuid])

        # Check custom_steps give same answers for breakdowns
        _, custom_step_results = ClickhouseFunnelActors(
            filter.with_data({"funnel_step_breakdown": "PL", "funnel_custom_steps": [1, 2, 3]}), self.team
        ).get_actors()
        self.assertEqual(results, custom_step_results)
Example #7
0
    def calculate_sessions(self, events: QuerySet, filter: Filter, team: Team) -> List[Dict[str, Any]]:
        all_sessions, sessions_sql_params = self.build_all_sessions_query(events)

        if filter.session == SESSION_AVG:
            if not filter.date_from:
                filter = filter.with_data(
                    {
                        "date_from": Event.objects.filter(team=team)
                        .order_by("timestamp")[0]
                        .timestamp.replace(hour=0, minute=0, second=0, microsecond=0)
                        .isoformat(),
                    }
                )
            return self._session_avg(all_sessions, sessions_sql_params, filter)
        else:  # SESSION_DIST
            return self._session_dist(all_sessions, sessions_sql_params)
Example #8
0
    def calculate_funnel_persons(
        self, request: request.Request
    ) -> Dict[str, Tuple[list, Optional[str], Optional[str]]]:
        if request.user.is_anonymous or not self.team:
            return {"result": ([], None, None)}

        filter = Filter(request=request, data={"insight": INSIGHT_FUNNELS}, team=self.team)
        if not filter.limit:
            filter = filter.with_data({LIMIT: 100})

        funnel_actor_class = get_funnel_actor_class(filter)

        actors, serialized_actors = funnel_actor_class(filter, self.team).get_actors()
        _should_paginate = should_paginate(actors, filter.limit)
        next_url = format_query_params_absolute_url(request, filter.offset + filter.limit) if _should_paginate else None
        initial_url = format_query_params_absolute_url(request, 0)

        # cached_function expects a dict with the key result
        return {"result": (serialized_actors, next_url, initial_url)}
Example #9
0
    def test_update_cache_item_calls_right_funnel_class_clickhouse(
        self,
        funnel_mock: MagicMock,
        funnel_trends_mock: MagicMock,
        funnel_time_to_convert_mock: MagicMock,
        funnel_strict_mock: MagicMock,
        funnel_unordered_mock: MagicMock,
    ) -> None:
        #  basic funnel
        base_filter = Filter(
            data={
                "insight":
                "FUNNELS",
                "events": [
                    {
                        "id": "$pageview",
                        "order": 0,
                        "type": "events"
                    },
                    {
                        "id": "$pageview",
                        "order": 1,
                        "type": "events"
                    },
                ],
            })

        with self.settings(EE_AVAILABLE=True, PRIMARY_DB="clickhouse"):
            filter = base_filter
            funnel_mock.return_value.run.return_value = {}
            update_cache_item(
                generate_cache_key("{}_{}".format(filter.toJSON(),
                                                  self.team.pk)),
                CacheType.FUNNEL,
                {
                    "filter": filter.toJSON(),
                    "team_id": self.team.pk,
                },
            )
            funnel_mock.assert_called_once()

            # trends funnel
            filter = base_filter.with_data({"funnel_viz_type": "trends"})
            funnel_trends_mock.return_value.run.return_value = {}
            update_cache_item(
                generate_cache_key("{}_{}".format(filter.toJSON(),
                                                  self.team.pk)),
                CacheType.FUNNEL,
                {
                    "filter": filter.toJSON(),
                    "team_id": self.team.pk,
                },
            )

            funnel_trends_mock.assert_called_once()
            self.assertEqual(
                funnel_trends_mock.call_args[1]["funnel_order_class"],
                funnel_mock)
            funnel_trends_mock.reset_mock()

            # trends unordered funnel
            filter = base_filter.with_data({
                "funnel_viz_type": "trends",
                "funnel_order_type": "unordered"
            })
            funnel_trends_mock.return_value.run.return_value = {}
            update_cache_item(
                generate_cache_key("{}_{}".format(filter.toJSON(),
                                                  self.team.pk)),
                CacheType.FUNNEL,
                {
                    "filter": filter.toJSON(),
                    "team_id": self.team.pk,
                },
            )

            funnel_trends_mock.assert_called_once()
            self.assertEqual(
                funnel_trends_mock.call_args[1]["funnel_order_class"],
                funnel_unordered_mock)
            funnel_trends_mock.reset_mock()

            # time to convert strict funnel
            filter = base_filter.with_data({
                "funnel_viz_type": "time_to_convert",
                "funnel_order_type": "strict"
            })
            funnel_time_to_convert_mock.return_value.run.return_value = {}
            update_cache_item(
                generate_cache_key("{}_{}".format(filter.toJSON(),
                                                  self.team.pk)),
                CacheType.FUNNEL,
                {
                    "filter": filter.toJSON(),
                    "team_id": self.team.pk,
                },
            )

            funnel_time_to_convert_mock.assert_called_once()
            self.assertEqual(
                funnel_time_to_convert_mock.call_args[1]["funnel_order_class"],
                funnel_strict_mock)
            funnel_time_to_convert_mock.reset_mock()

            # strict funnel
            filter = base_filter.with_data({"funnel_order_type": "strict"})
            funnel_strict_mock.return_value.run.return_value = {}
            update_cache_item(
                generate_cache_key("{}_{}".format(filter.toJSON(),
                                                  self.team.pk)),
                CacheType.FUNNEL,
                {
                    "filter": filter.toJSON(),
                    "team_id": self.team.pk,
                },
            )

            funnel_strict_mock.assert_called_once()
Example #10
0
    def test_first_step_breakdowns(self):

        person1 = _create_person(distinct_ids=["person1"],
                                 team_id=self.team.pk)
        _create_event(
            team=self.team,
            event="sign up",
            distinct_id="person1",
            properties={
                "key": "val",
                "$browser": "Chrome"
            },
            timestamp="2020-01-01T12:00:00Z",
        )
        _create_event(
            team=self.team,
            event="play movie",
            distinct_id="person1",
            properties={
                "key": "val",
                "$browser": "Chrome"
            },
            timestamp="2020-01-01T13:00:00Z",
        )
        _create_event(
            team=self.team,
            event="buy",
            distinct_id="person1",
            properties={
                "key": "val",
                "$browser": "Chrome"
            },
            timestamp="2020-01-01T15:00:00Z",
        )

        person2 = _create_person(distinct_ids=["person2"],
                                 team_id=self.team.pk)
        _create_event(
            team=self.team,
            event="sign up",
            distinct_id="person2",
            properties={
                "key": "val",
                "$browser": "Safari"
            },
            timestamp="2020-01-02T14:00:00Z",
        )
        _create_event(
            team=self.team,
            event="play movie",
            distinct_id="person2",
            properties={
                "key": "val",
                "$browser": "Safari"
            },
            timestamp="2020-01-02T16:00:00Z",
        )

        data = {
            "insight":
            INSIGHT_FUNNELS,
            "date_from":
            "2020-01-01",
            "date_to":
            "2020-01-08",
            "interval":
            "day",
            "funnel_window_days":
            7,
            "funnel_step":
            1,
            "events": [
                {
                    "id": "sign up",
                    "order": 0
                },
                {
                    "id": "play movie",
                    "order": 1
                },
                {
                    "id": "buy",
                    "order": 2
                },
            ],
            "breakdown_type":
            "event",
            "breakdown":
            "$browser",
        }
        filter = Filter(data=data)
        results = ClickhouseFunnelPersons(filter, self.team)._exec_query()

        self.assertCountEqual([val[0] for val in results],
                              [person1.uuid, person2.uuid])

        results = ClickhouseFunnelPersons(
            filter.with_data({"funnel_step_breakdown": "Chrome"}),
            self.team)._exec_query()
        print(results)
        self.assertCountEqual([val[0] for val in results], [person1.uuid])

        results = ClickhouseFunnelPersons(
            filter.with_data({"funnel_step_breakdown": "Safari"}),
            self.team)._exec_query()
        self.assertCountEqual([val[0] for val in results], [person2.uuid])

        results = ClickhouseFunnelPersons(
            filter.with_data({"funnel_step_breakdown": "Safari, Chrome"}),
            self.team)._exec_query()
        self.assertCountEqual([val[0] for val in results],
                              [person2.uuid, person1.uuid])
    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)

        success_target_persons = []
        failure_target_persons = []

        for i in range(10):
            person = _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",
                )
                success_target_persons.append(str(person.uuid))

            _create_event(
                team=self.team,
                event="paid",
                distinct_id=f"user_{i}",
                timestamp="2020-01-04T14:00:00Z",
            )

        for i in range(10, 20):
            person = _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",
                )
                failure_target_persons.append(str(person.uuid))

        # One positively_related as failure
        person_fail = _create_person(distinct_ids=[f"user_fail"],
                                     team_id=self.team.pk)
        _create_event(
            team=self.team,
            event="user signed up",
            distinct_id=f"user_fail",
            timestamp="2020-01-02T14:00:00Z",
        )
        _create_event(
            team=self.team,
            event="positively_related",
            distinct_id=f"user_fail",
            timestamp="2020-01-03T14:00:00Z",
        )

        # One negatively_related as success
        person_succ = _create_person(distinct_ids=[f"user_succ"],
                                     team_id=self.team.pk)
        _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="negatively_related",
            distinct_id=f"user_succ",
            timestamp="2020-01-03T14:00:00Z",
        )
        _create_event(
            team=self.team,
            event="paid",
            distinct_id=f"user_succ",
            timestamp="2020-01-04T14:00:00Z",
        )

        # TESTS

        # test positively_related successes
        filter = filter.with_data({
            "funnel_correlation_person_entity": {
                "id": "positively_related",
                "type": "events"
            },
            "funnel_correlation_person_converted":
            "TrUe",
        })
        results, has_more_results = FunnelCorrelationPersons(
            filter, self.team).run()

        self.assertFalse(has_more_results)
        self.assertCountEqual([val["uuid"] for val in results],
                              success_target_persons)

        # test negatively_related failures
        filter = filter.with_data({
            "funnel_correlation_person_entity": {
                "id": "negatively_related",
                "type": "events"
            },
            "funnel_correlation_person_converted":
            "falsE",
        })

        results, has_more_results = FunnelCorrelationPersons(
            filter, self.team).run()

        self.assertFalse(has_more_results)
        self.assertCountEqual([val["uuid"] for val in results],
                              failure_target_persons)

        # test positively_related failures
        filter = filter.with_data({
            "funnel_correlation_person_entity": {
                "id": "positively_related",
                "type": "events"
            },
            "funnel_correlation_person_converted":
            "False",
        })
        results, has_more_results = FunnelCorrelationPersons(
            filter, self.team).run()

        self.assertFalse(has_more_results)
        self.assertCountEqual([val["uuid"] for val in results],
                              [str(person_fail.uuid)])

        # test negatively_related successes
        filter = filter.with_data({
            "funnel_correlation_person_entity": {
                "id": "negatively_related",
                "type": "events"
            },
            "funnel_correlation_person_converted":
            "trUE",
        })
        results, has_more_results = FunnelCorrelationPersons(
            filter, self.team).run()

        self.assertFalse(has_more_results)
        self.assertCountEqual([val["uuid"] for val in results],
                              [str(person_succ.uuid)])

        # test all positively_related
        filter = filter.with_data({
            "funnel_correlation_person_entity": {
                "id": "positively_related",
                "type": "events"
            },
            "funnel_correlation_person_converted": None,
        })
        results, has_more_results = FunnelCorrelationPersons(
            filter, self.team).run()

        self.assertFalse(has_more_results)
        self.assertCountEqual([val["uuid"] for val in results],
                              [*success_target_persons,
                               str(person_fail.uuid)])

        # test all negatively_related
        filter = filter.with_data({
            "funnel_correlation_person_entity": {
                "id": "negatively_related",
                "type": "events"
            },
            "funnel_correlation_person_converted": None,
        })
        results, has_more_results = FunnelCorrelationPersons(
            filter, self.team).run()

        self.assertFalse(has_more_results)
        self.assertCountEqual([val["uuid"] for val in results],
                              [*failure_target_persons,
                               str(person_succ.uuid)])