diff --git a/frontend/src/scenes/trends/PeopleModal.js b/frontend/src/scenes/trends/PeopleModal.js index ef63a7bc633..ca6fe15b86f 100644 --- a/frontend/src/scenes/trends/PeopleModal.js +++ b/frontend/src/scenes/trends/PeopleModal.js @@ -2,12 +2,12 @@ import React from 'react' import { useActions, useValues } from 'kea' import moment from 'moment' import { trendsLogic } from 'scenes/trends/trendsLogic' -import { Modal, Button } from 'antd' +import { Modal, Button, Spin } from 'antd' import { PeopleTable } from 'scenes/users/PeopleTable' export function PeopleModal({ visible }) { const { people, filters } = useValues(trendsLogic({ id: null })) - const { setShowingPeople } = useActions(trendsLogic({ dashboardItemId: null })) + const { setShowingPeople, loadMorePeople } = useActions(trendsLogic({ dashboardItemId: null })) const title = filters.shown_as === 'Stickiness' @@ -26,13 +26,24 @@ export function PeopleModal({ visible }) { {people ? (

Found {people.count} {people.count === 1 ? 'user' : 'users'} - {people.count > 100 ? '. Showing the first 100 below.' : ''}

) : (

Loading users...

)} +
+ {people?.next && ( + + )} +
) } diff --git a/frontend/src/scenes/trends/trendsLogic.js b/frontend/src/scenes/trends/trendsLogic.js index 7a8e14be832..c0c286bc09e 100644 --- a/frontend/src/scenes/trends/trendsLogic.js +++ b/frontend/src/scenes/trends/trendsLogic.js @@ -81,6 +81,32 @@ function autocorrectInterval({ date_from, interval }) { } } +function parsePeopleParams(peopleParams, filters) { + const { action, day, breakdown_value } = peopleParams + const params = filterClientSideParams({ + ...filters, + entityId: action.id, + type: action.type, + breakdown_value, + }) + + if (filters.shown_as === STICKINESS) { + params.stickiness_days = day + } else if (params.display === ACTIONS_LINE_GRAPH_CUMULATIVE) { + params.date_to = day + } else { + params.date_from = day + params.date_to = day + } + // If breakdown type is cohort, we use breakdown_value + // If breakdown type is event, we just set another filter + if (breakdown_value && filters.breakdown_type != 'cohort') { + params.properties = [...params.properties, { key: params.breakdown, value: breakdown_value, type: 'event' }] + } + + return toAPIParams(params) +} + // props: // - dashboardItemId // - filters @@ -121,14 +147,17 @@ export const trendsLogic = kea({ setDisplay: display => ({ display }), loadPeople: (action, label, day, breakdown_value) => ({ action, label, day, breakdown_value }), + loadMorePeople: true, + setLoadingMorePeople: status => ({ status }), setShowingPeople: isShowing => ({ isShowing }), - setPeople: (people, count, action, label, day, breakdown_value) => ({ + setPeople: (people, count, action, label, day, breakdown_value, next) => ({ people, count, action, label, day, breakdown_value, + next, }), setActiveView: type => ({ type }), setCachedUrl: (type, url) => ({ type, url }), @@ -151,6 +180,7 @@ export const trendsLogic = kea({ { [actions.setFilters]: () => null, [actions.setPeople]: (_, people) => people, + [actions.setLoadingMorePeople]: (state, { status }) => ({ ...state, loadingMore: status }), }, ], cachedUrls: [ @@ -183,35 +213,35 @@ export const trendsLogic = kea({ actions.setFilters({ display }) }, [actions.loadPeople]: async ({ label, action, day, breakdown_value }, breakpoint) => { - const params = filterClientSideParams({ - ...values.filters, - entityId: action.id, - type: action.type, - breakdown_value, - }) - - if (values.filters.shown_as === STICKINESS) { - params.stickiness_days = day - } else if (params.display === ACTIONS_LINE_GRAPH_CUMULATIVE) { - params.date_to = day - } else { - params.date_from = day - params.date_to = day - } - // If breakdown type is cohort, we use breakdown_value - // If breakdown type is event, we just set another filter - if (breakdown_value && values.filters.breakdown_type != 'cohort') { - params.properties = [ - ...params.properties, - { key: params.breakdown, value: breakdown_value, type: 'event' }, - ] - } - - const filterParams = toAPIParams(params) - actions.setPeople(null, null, action, label, day, breakdown_value) + const filterParams = parsePeopleParams({ label, action, day, breakdown_value }, values.filters) + actions.setPeople(null, null, action, label, day, breakdown_value, null) const people = await api.get(`api/action/people/?include_last_event=1&${filterParams}`) breakpoint() - actions.setPeople(people[0]?.people, people[0]?.count, action, label, day, breakdown_value) + actions.setPeople( + people.results[0]?.people, + people.results[0]?.count, + action, + label, + day, + breakdown_value, + people.next + ) + }, + [actions.loadMorePeople]: async (_, breakpoint) => { + const { people: currPeople, count, action, label, day, breakdown_value, next } = values.people + actions.setLoadingMorePeople(true) + const people = await api.get(next) + actions.setLoadingMorePeople(false) + breakpoint() + actions.setPeople( + [...currPeople, ...people.results[0]?.people], + count + people.results[0]?.count, + action, + label, + day, + breakdown_value, + people.next + ) }, }), diff --git a/frontend/src/scenes/users/PeopleTable.js b/frontend/src/scenes/users/PeopleTable.js index b7bdc17e6a2..2028bdd9e9d 100644 --- a/frontend/src/scenes/users/PeopleTable.js +++ b/frontend/src/scenes/users/PeopleTable.js @@ -38,7 +38,7 @@ export function PeopleTable({ people, loading, actions, onChange }) { columns={columns} loading={loading} rowKey={person => person.id} - pagination={{ pageSize: 100, hideOnSinglePage: true }} + pagination={{ pageSize: 99999, hideOnSinglePage: true }} expandable={{ expandedRowRender: function RenderPropertiesTable({ properties }) { return diff --git a/posthog/api/action.py b/posthog/api/action.py index b0bb1550197..8bdd87219e5 100644 --- a/posthog/api/action.py +++ b/posthog/api/action.py @@ -105,7 +105,9 @@ def get_actions(queryset: QuerySet, params: dict, team_id: int) -> QuerySet: queryset = queryset.filter( pk__in=[ action.id - for action in Filter({"actions": json.loads(params.get("actions", "[]"))}).actions + for action in Filter( + {"actions": json.loads(params.get("actions", "[]"))} + ).actions ] ) @@ -131,7 +133,9 @@ class ActionViewSet(viewsets.ModelViewSet): queryset = super().get_queryset() if self.action == "list": # type: ignore queryset = queryset.filter(deleted=False) - return get_actions(queryset, self.request.GET.dict(), self.request.user.team_set.get().pk) + return get_actions( + queryset, self.request.GET.dict(), self.request.user.team_set.get().pk + ) def create(self, request: request.Request, *args: Any, **kwargs: Any) -> Response: action, created = Action.objects.get_or_create( @@ -144,7 +148,9 @@ class ActionViewSet(viewsets.ModelViewSet): }, ) if not created: - return Response(data={"detail": "action-exists", "id": action.pk}, status=400) + return Response( + data={"detail": "action-exists", "id": action.pk}, status=400 + ) if request.data.get("steps"): for step in request.data["steps"]: @@ -195,7 +201,9 @@ class ActionViewSet(viewsets.ModelViewSet): actions = self.get_queryset() actions_list: List[Dict[Any, Any]] = ActionSerializer(actions, many=True, context={"request": request}).data # type: ignore if request.GET.get("include_count", False): - actions_list.sort(key=lambda action: action.get("count", action["id"]), reverse=True) + actions_list.sort( + key=lambda action: action.get("count", action["id"]), reverse=True + ) return Response({"results": actions_list}) @action(methods=["GET"], detail=False) @@ -223,14 +231,17 @@ class ActionViewSet(viewsets.ModelViewSet): def people(self, request: request.Request, *args: Any, **kwargs: Any) -> Response: team = request.user.team_set.get() filter = Filter(request=request) + offset = int(request.GET.get("offset", 0)) - def _calculate_people(events: QuerySet): + def _calculate_people(events: QuerySet, offset: int): shown_as = request.GET.get("shown_as") if shown_as is not None and shown_as == "Stickiness": stickiness_days = int(request.GET["stickiness_days"]) events = ( events.values("person_id") - .annotate(day_count=Count(functions.TruncDay("timestamp"), distinct=True)) + .annotate( + day_count=Count(functions.TruncDay("timestamp"), distinct=True) + ) .filter(day_count=stickiness_days) ) else: @@ -250,7 +261,8 @@ class ActionViewSet(viewsets.ModelViewSet): ) people = Person.objects.filter( - team=team, id__in=[p["person_id"] for p in events[0:100]] + team=team, + id__in=[p["person_id"] for p in events[offset : offset + 100]], ) people = people.prefetch_related( @@ -270,7 +282,9 @@ class ActionViewSet(viewsets.ModelViewSet): if len(filter.entities) >= 1: entity = filter.entities[0] else: - entity = Entity({"id": request.GET["entityId"], "type": request.GET["type"]}) + entity = Entity( + {"id": request.GET["entityId"], "type": request.GET["type"]} + ) if entity.type == TREND_FILTER_TYPE_EVENTS: filtered_events = process_entity_for_events( @@ -287,8 +301,28 @@ class ActionViewSet(viewsets.ModelViewSet): entity, team_id=team.pk, order_by=None ).filter(filter_events(team.pk, filter, entity)) - people = _calculate_people(events=filtered_events) - return Response([people]) + people = _calculate_people(events=filtered_events, offset=offset) + + current_url = request.get_full_path() + next_url: Optional[str] = request.get_full_path() + if people["count"] > 99 and next_url: + if "offset" in next_url: + next_url = next_url[1:] + next_url = next_url.replace( + "offset=" + str(offset), "offset=" + str(offset + 100) + ) + else: + next_url = request.build_absolute_uri( + "{}{}offset={}".format( + next_url, "&" if "?" in next_url else "?", offset + 100 + ) + ) + else: + next_url = None + + return Response( + {"results": [people], "next": next_url, "previous": current_url[1:]} + ) def calculate_trends( @@ -301,7 +335,13 @@ def calculate_trends( if len(filter.entities) == 0: # If no filters, automatically grab all actions and show those instead filter.entities = [ - Entity({"id": action.id, "name": action.name, "type": TREND_FILTER_TYPE_ACTIONS,}) + Entity( + { + "id": action.id, + "name": action.name, + "type": TREND_FILTER_TYPE_ACTIONS, + } + ) for action in actions ] @@ -340,7 +380,9 @@ def calculate_trends( ) compared_trend_entity = convert_to_comparison( - compared_trend_entity, compared_filter, "{} - {}".format(entity.name, "previous"), + compared_trend_entity, + compared_filter, + "{} - {}".format(entity.name, "previous"), ) entities_list.extend(compared_trend_entity) else: @@ -378,9 +420,13 @@ def build_dataframe( ] ) if interval == "week": - dataframe["date"] = dataframe["date"].apply(lambda x: x - pd.offsets.Week(weekday=6)) + dataframe["date"] = dataframe["date"].apply( + lambda x: x - pd.offsets.Week(weekday=6) + ) elif interval == "month": - dataframe["date"] = dataframe["date"].apply(lambda x: x - pd.offsets.MonthEnd(n=1)) + dataframe["date"] = dataframe["date"].apply( + lambda x: x - pd.offsets.MonthEnd(n=1) + ) return dataframe @@ -415,13 +461,15 @@ def group_events_to_date( df_dates = pd.DataFrame(filtered.groupby("date").mean(), index=time_index) df_dates = df_dates.fillna(0) response[value] = { - key: value[0] if len(value) > 0 else 0 for key, value in df_dates.iterrows() + key: value[0] if len(value) > 0 else 0 + for key, value in df_dates.iterrows() } else: dataframe = pd.DataFrame([], index=time_index) dataframe = dataframe.fillna(0) response["total"] = { - key: value[0] if len(value) > 0 else 0 for key, value in dataframe.iterrows() + key: value[0] if len(value) > 0 else 0 + for key, value in dataframe.iterrows() } return response @@ -444,13 +492,15 @@ def get_interval_annotation(key: str) -> Dict[str, Any]: def add_cohort_annotations( team_id: int, breakdown: List[Union[int, str]] ) -> Dict[str, Union[Value, Exists]]: - cohorts = Cohort.objects.filter(team_id=team_id, pk__in=[b for b in breakdown if b != "all"]) + cohorts = Cohort.objects.filter( + team_id=team_id, pk__in=[b for b in breakdown if b != "all"] + ) annotations: Dict[str, Union[Value, Exists]] = {} for cohort in cohorts: annotations["cohort_{}".format(cohort.pk)] = Exists( - CohortPeople.objects.filter(cohort=cohort.pk, person_id=OuterRef("person_id")).only( - "id" - ) + CohortPeople.objects.filter( + cohort=cohort.pk, person_id=OuterRef("person_id") + ).only("id") ) if "all" in breakdown: annotations["cohort_all"] = Value(True, output_field=BooleanField()) @@ -470,7 +520,9 @@ def aggregate_by_interval( values = [interval] if breakdown: if params.get("breakdown_type") == "cohort": - annotations = add_cohort_annotations(team_id, json.loads(params.get("breakdown", "[]"))) + annotations = add_cohort_annotations( + team_id, json.loads(params.get("breakdown", "[]")) + ) values.extend(annotations.keys()) filtered_events = filtered_events.annotate(**annotations) breakdown = "cohorts" @@ -551,11 +603,14 @@ def stickiness( } -def breakdown_label(entity: Entity, value: Union[str, int]) -> Dict[str, Optional[Union[str, int]]]: +def breakdown_label( + entity: Entity, value: Union[str, int] +) -> Dict[str, Optional[Union[str, int]]]: ret_dict: Dict[str, Optional[Union[str, int]]] = {} if not value or not isinstance(value, str) or "cohort_" not in value: ret_dict["label"] = "{} - {}".format( - entity.name, value if value and value != "None" and value != "nan" else "Other", + entity.name, + value if value and value != "None" and value != "nan" else "Other", ) ret_dict["breakdown_value"] = value if value and not pd.isna(value) else None else: @@ -607,14 +662,18 @@ def serialize_entity( new_dict = copy.deepcopy(serialized) if value != "Total": new_dict.update(breakdown_label(entity, value)) - new_dict.update(append_data(dates_filled=list(item.items()), interval=interval)) + new_dict.update( + append_data(dates_filled=list(item.items()), interval=interval) + ) if filter.display == TRENDS_CUMULATIVE: new_dict["data"] = np.cumsum(new_dict["data"]) response.append(new_dict) elif params.get("shown_as") == TRENDS_STICKINESS: new_dict = copy.deepcopy(serialized) new_dict.update( - stickiness(filtered_events=events, entity=entity, filter=filter, team_id=team_id) + stickiness( + filtered_events=events, entity=entity, filter=filter, team_id=team_id + ) ) response.append(new_dict) @@ -622,7 +681,9 @@ def serialize_entity( def serialize_people(people: QuerySet, request: request.Request) -> Dict: - people_dict = [PersonSerializer(person, context={"request": request}).data for person in people] + people_dict = [ + PersonSerializer(person, context={"request": request}).data for person in people + ] return {"people": people_dict, "count": len(people_dict)} diff --git a/posthog/api/test/test_action.py b/posthog/api/test/test_action.py index 8020245814f..866f8a1c7ea 100644 --- a/posthog/api/test/test_action.py +++ b/posthog/api/test/test_action.py @@ -7,75 +7,101 @@ from posthog.models import Action, ActionStep, Element, Event, Person, Team, Coh from .base import BaseTest, TransactionBaseTest -@patch('posthog.tasks.calculate_action.calculate_action.delay') +@patch("posthog.tasks.calculate_action.calculate_action.delay") class TestCreateAction(BaseTest): TESTS_API = True def test_create_and_update_action(self, patch_delay): - Event.objects.create(team=self.team, event='$autocapture', elements=[ - Element(tag_name='button', order=0, text='sign up NOW'), - Element(tag_name='div', order=1), - ]) - response = self.client.post('/api/action/', data={ - 'name': 'user signed up', - 'steps': [{ - "text": "sign up", - "selector": "div > button", - "url": "/signup", - "isNew": 'asdf', - }], - }, content_type='application/json', HTTP_ORIGIN='http://testserver').json() + Event.objects.create( + team=self.team, + event="$autocapture", + elements=[ + Element(tag_name="button", order=0, text="sign up NOW"), + Element(tag_name="div", order=1), + ], + ) + response = self.client.post( + "/api/action/", + data={ + "name": "user signed up", + "steps": [ + { + "text": "sign up", + "selector": "div > button", + "url": "/signup", + "isNew": "asdf", + } + ], + }, + content_type="application/json", + HTTP_ORIGIN="http://testserver", + ).json() action = Action.objects.get() - self.assertEqual(action.name, 'user signed up') + self.assertEqual(action.name, "user signed up") self.assertEqual(action.team, self.team) - self.assertEqual(action.steps.get().selector, 'div > button') - self.assertEqual(response['steps'][0]['text'], 'sign up') + self.assertEqual(action.steps.get().selector, "div > button") + self.assertEqual(response["steps"][0]["text"], "sign up") # test no actions with same name - user2 = self._create_user('tim2') + user2 = self._create_user("tim2") self.client.force_login(user2) response = self.client.post( - '/api/action/', - data={'name': 'user signed up'}, - content_type='application/json', - HTTP_ORIGIN='http://testserver', + "/api/action/", + data={"name": "user signed up"}, + content_type="application/json", + HTTP_ORIGIN="http://testserver", ).json() - self.assertEqual(response['detail'], 'action-exists') + self.assertEqual(response["detail"], "action-exists") # test update - event2 = Event.objects.create(team=self.team, event='$autocapture', properties={'$browser': 'Chrome'}, elements=[ - Element(tag_name='button', order=0, text='sign up NOW'), - Element(tag_name='div', order=1), - ]) - response = self.client.patch('/api/action/%s/' % action.pk, data={ - 'name': 'user signed up 2', - 'steps': [{ - "id": action.steps.get().pk, - "isNew": "asdf", - "text": "sign up NOW", - "selector": "div > button", - "properties": [{'key': '$browser', 'value': 'Chrome'}], - "url": None, - }, {'href': '/a-new-link'}], - }, content_type='application/json', HTTP_ORIGIN='http://testserver').json() + event2 = Event.objects.create( + team=self.team, + event="$autocapture", + properties={"$browser": "Chrome"}, + elements=[ + Element(tag_name="button", order=0, text="sign up NOW"), + Element(tag_name="div", order=1), + ], + ) + response = self.client.patch( + "/api/action/%s/" % action.pk, + data={ + "name": "user signed up 2", + "steps": [ + { + "id": action.steps.get().pk, + "isNew": "asdf", + "text": "sign up NOW", + "selector": "div > button", + "properties": [{"key": "$browser", "value": "Chrome"}], + "url": None, + }, + {"href": "/a-new-link"}, + ], + }, + content_type="application/json", + HTTP_ORIGIN="http://testserver", + ).json() action = Action.objects.get() action.calculate_events() - steps = action.steps.all().order_by('id') - self.assertEqual(action.name, 'user signed up 2') - self.assertEqual(steps[0].text, 'sign up NOW') - self.assertEqual(steps[1].href, '/a-new-link') + steps = action.steps.all().order_by("id") + self.assertEqual(action.name, "user signed up 2") + self.assertEqual(steps[0].text, "sign up NOW") + self.assertEqual(steps[1].href, "/a-new-link") self.assertEqual(action.events.get(), event2) self.assertEqual(action.events.count(), 1) # test queries with self.assertNumQueries(5): - response = self.client.get('/api/action/') + response = self.client.get("/api/action/") # test remove steps - response = self.client.patch('/api/action/%s/' % action.pk, data={ - 'name': 'user signed up 2', - 'steps': [], - }, content_type='application/json', HTTP_ORIGIN='http://testserver').json() + response = self.client.patch( + "/api/action/%s/" % action.pk, + data={"name": "user signed up 2", "steps": [],}, + content_type="application/json", + HTTP_ORIGIN="http://testserver", + ).json() self.assertEqual(ActionStep.objects.count(), 0) # When we send a user to their own site, we give them a token. @@ -85,57 +111,73 @@ class TestCreateAction(BaseTest): def test_create_from_other_domain(self, patch_delay): # FIXME: BaseTest is using Django client to performe calls to a DRF endpoint. # Django HttpResponse does not have an attribute `data`. Better use rest_framework.test.APIClient. - response = self.client.post('/api/action/', data={ - 'name': 'user signed up', - }, content_type='application/json', HTTP_ORIGIN='https://evilwebsite.com') + response = self.client.post( + "/api/action/", + data={"name": "user signed up",}, + content_type="application/json", + HTTP_ORIGIN="https://evilwebsite.com", + ) self.assertEqual(response.status_code, 403) - self.user.temporary_token = 'token123' + self.user.temporary_token = "token123" self.user.save() - response = self.client.post('/api/action/?temporary_token=token123', data={ - 'name': 'user signed up', - }, content_type='application/json', HTTP_ORIGIN='https://somewebsite.com') + response = self.client.post( + "/api/action/?temporary_token=token123", + data={"name": "user signed up",}, + content_type="application/json", + HTTP_ORIGIN="https://somewebsite.com", + ) self.assertEqual(response.status_code, 200) - response = self.client.post('/api/action/?temporary_token=token123', data={ - 'name': 'user signed up and post to slack', - 'post_to_slack': True, - }, content_type='application/json', HTTP_ORIGIN='https://somewebsite.com') + response = self.client.post( + "/api/action/?temporary_token=token123", + data={"name": "user signed up and post to slack", "post_to_slack": True,}, + content_type="application/json", + HTTP_ORIGIN="https://somewebsite.com", + ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()['post_to_slack'], True) + self.assertEqual(response.json()["post_to_slack"], True) - list_response = self.client.get('/api/action/', content_type='application/json', HTTP_ORIGIN='https://evilwebsite.com') + list_response = self.client.get( + "/api/action/", + content_type="application/json", + HTTP_ORIGIN="https://evilwebsite.com", + ) self.assertEqual(list_response.status_code, 403) detail_response = self.client.get( f"/api/action/{response.json()['id']}/", - content_type='application/json', - HTTP_ORIGIN='https://evilwebsite.com', + content_type="application/json", + HTTP_ORIGIN="https://evilwebsite.com", ) self.assertEqual(detail_response.status_code, 403) self.client.logout() list_response = self.client.get( - '/api/action/', - data={ - 'temporary_token': 'token123', - }, - content_type='application/json', - HTTP_ORIGIN='https://somewebsite.com', + "/api/action/", + data={"temporary_token": "token123",}, + content_type="application/json", + HTTP_ORIGIN="https://somewebsite.com", ) self.assertEqual(list_response.status_code, 200) - response = self.client.post('/api/action/?temporary_token=token123', data={ - 'name': 'user signed up 22', - }, content_type='application/json', HTTP_ORIGIN='https://somewebsite.com') + response = self.client.post( + "/api/action/?temporary_token=token123", + data={"name": "user signed up 22",}, + content_type="application/json", + HTTP_ORIGIN="https://somewebsite.com", + ) self.assertEqual(response.status_code, 200, response.json()) # This case happens when someone is running behind a proxy, but hasn't set `IS_BEHIND_PROXY` def test_http_to_https(self, patch_delay): - response = self.client.post('/api/action/', data={ - 'name': 'user signed up again', - }, content_type='application/json', HTTP_ORIGIN='https://testserver/') + response = self.client.post( + "/api/action/", + data={"name": "user signed up again",}, + content_type="application/json", + HTTP_ORIGIN="https://testserver/", + ) self.assertEqual(response.status_code, 200, response.json()) @@ -143,58 +185,88 @@ class TestTrends(TransactionBaseTest): TESTS_API = True def _create_events(self, use_time=False): - no_events = Action.objects.create(team=self.team, name='no events') - ActionStep.objects.create(action=no_events, event='no events') + no_events = Action.objects.create(team=self.team, name="no events") + ActionStep.objects.create(action=no_events, event="no events") - sign_up_action = Action.objects.create(team=self.team, name='sign up') - ActionStep.objects.create(action=sign_up_action, event='sign up') + sign_up_action = Action.objects.create(team=self.team, name="sign up") + ActionStep.objects.create(action=sign_up_action, event="sign up") - person = Person.objects.create(team=self.team, distinct_ids=['blabla', 'anonymous_id']) - secondTeam = Team.objects.create(api_token='token123') + person = Person.objects.create( + team=self.team, distinct_ids=["blabla", "anonymous_id"] + ) + secondTeam = Team.objects.create(api_token="token123") - freeze_without_time = ['2019-12-24', '2020-01-01', '2020-01-02'] - freeze_with_time = ['2019-12-24 03:45:34', '2020-01-01 00:06:34', '2020-01-02 16:34:34'] + freeze_without_time = ["2019-12-24", "2020-01-01", "2020-01-02"] + freeze_with_time = [ + "2019-12-24 03:45:34", + "2020-01-01 00:06:34", + "2020-01-02 16:34:34", + ] freeze_args = freeze_without_time if use_time: freeze_args = freeze_with_time with freeze_time(freeze_args[0]): - Event.objects.create(team=self.team, event='sign up', distinct_id='blabla', properties={"$some_property": "value"}) + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="blabla", + properties={"$some_property": "value"}, + ) with freeze_time(freeze_args[1]): - Event.objects.create(team=self.team, event='sign up', distinct_id='blabla', properties={"$some_property": "value"}) - Event.objects.create(team=self.team, event='sign up', distinct_id='anonymous_id') - Event.objects.create(team=self.team, event='sign up', distinct_id='blabla') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="blabla", + properties={"$some_property": "value"}, + ) + Event.objects.create( + team=self.team, event="sign up", distinct_id="anonymous_id" + ) + Event.objects.create(team=self.team, event="sign up", distinct_id="blabla") with freeze_time(freeze_args[2]): Event.objects.create( team=self.team, - event='sign up', - distinct_id='blabla', - properties={"$some_property": "other_value", "$some_numerical_prop": 80}, + event="sign up", + distinct_id="blabla", + properties={ + "$some_property": "other_value", + "$some_numerical_prop": 80, + }, + ) + Event.objects.create( + team=self.team, event="no events", distinct_id="blabla" ) - Event.objects.create(team=self.team, event='no events', distinct_id='blabla') # second team should have no effect Event.objects.create( team=secondTeam, - event='sign up', - distinct_id='blabla', + event="sign up", + distinct_id="blabla", properties={"$some_property": "other_value"}, ) return sign_up_action, person def _create_breakdown_events(self): - freeze_without_time = ['2020-01-02'] + freeze_without_time = ["2020-01-02"] - sign_up_action = Action.objects.create(team=self.team, name='sign up') - ActionStep.objects.create(action=sign_up_action, event='sign up') + sign_up_action = Action.objects.create(team=self.team, name="sign up") + ActionStep.objects.create(action=sign_up_action, event="sign up") with freeze_time(freeze_without_time[0]): for i in range(25): - Event.objects.create(team=self.team, event='sign up', distinct_id='blabla', properties={"$some_property": i}) + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="blabla", + properties={"$some_property": i}, + ) - def _compare_entity_response(self, response1, response2, remove=('action', 'label')): + def _compare_entity_response( + self, response1, response2, remove=("action", "label") + ): if len(response1): for attr in remove: response1[0].pop(attr) @@ -209,115 +281,120 @@ class TestTrends(TransactionBaseTest): def test_trends_per_day(self): self._create_events() - with freeze_time('2020-01-04T13:00:01Z'): + with freeze_time("2020-01-04T13:00:01Z"): with self.assertNumQueries(16): - action_response = self.client.get('/api/action/trends/?date_from=-7d').json() + action_response = self.client.get( + "/api/action/trends/?date_from=-7d" + ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'date_from': '-7d', - 'events': jdumps([{'id': "sign up"}, {'id': "no events"}]), + "date_from": "-7d", + "events": jdumps([{"id": "sign up"}, {"id": "no events"}]), }, ).json() - self.assertEqual(action_response[0]['label'], 'sign up') - self.assertEqual(action_response[0]['labels'][4], 'Wed. 1 January') - self.assertEqual(action_response[0]['data'][4], 3.0) - self.assertEqual(action_response[0]['labels'][5], 'Thu. 2 January') - self.assertEqual(action_response[0]['data'][5], 1.0) - self.assertEqual(event_response[0]['label'], 'sign up') + self.assertEqual(action_response[0]["label"], "sign up") + self.assertEqual(action_response[0]["labels"][4], "Wed. 1 January") + self.assertEqual(action_response[0]["data"][4], 3.0) + self.assertEqual(action_response[0]["labels"][5], "Thu. 2 January") + self.assertEqual(action_response[0]["data"][5], 1.0) + self.assertEqual(event_response[0]["label"], "sign up") self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_trends_per_day_cumulative(self): self._create_events() - with freeze_time('2020-01-04T13:00:01Z'): + with freeze_time("2020-01-04T13:00:01Z"): with self.assertNumQueries(16): - action_response = self.client.get('/api/action/trends/?date_from=-7d&display=ActionsLineGraphCumulative').json() + action_response = self.client.get( + "/api/action/trends/?date_from=-7d&display=ActionsLineGraphCumulative" + ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'date_from': '-7d', - 'events': jdumps([{'id': "sign up"}, {'id': "no events"}]), - 'display': 'ActionsLineGraphCumulative' + "date_from": "-7d", + "events": jdumps([{"id": "sign up"}, {"id": "no events"}]), + "display": "ActionsLineGraphCumulative", }, ).json() - self.assertEqual(action_response[0]['label'], 'sign up') - self.assertEqual(action_response[0]['labels'][4], 'Wed. 1 January') - self.assertEqual(action_response[0]['data'][4], 3.0) - self.assertEqual(action_response[0]['labels'][5], 'Thu. 2 January') - self.assertEqual(action_response[0]['data'][5], 4.0) - self.assertEqual(event_response[0]['label'], 'sign up') + self.assertEqual(action_response[0]["label"], "sign up") + self.assertEqual(action_response[0]["labels"][4], "Wed. 1 January") + self.assertEqual(action_response[0]["data"][4], 3.0) + self.assertEqual(action_response[0]["labels"][5], "Thu. 2 January") + self.assertEqual(action_response[0]["data"][5], 4.0) + self.assertEqual(event_response[0]["label"], "sign up") self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_trends_compare(self): self._create_events() - with freeze_time('2020-01-04T13:00:01Z'): - action_response = self.client.get('/api/action/trends/?date_from=-7d&compare=true').json() + with freeze_time("2020-01-04T13:00:01Z"): + action_response = self.client.get( + "/api/action/trends/?date_from=-7d&compare=true" + ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'date_from': '-7d', - 'events': jdumps([{'id': "sign up"}, {'id': "no events"}]), - 'compare': 'true' + "date_from": "-7d", + "events": jdumps([{"id": "sign up"}, {"id": "no events"}]), + "compare": "true", }, ).json() - self.assertEqual(action_response[0]['label'], 'sign up - current') - self.assertEqual(action_response[0]['labels'][4], 'day 4') - self.assertEqual(action_response[0]['data'][4], 3.0) - self.assertEqual(action_response[0]['labels'][5], 'day 5') - self.assertEqual(action_response[0]['data'][5], 1.0) + self.assertEqual(action_response[0]["label"], "sign up - current") + self.assertEqual(action_response[0]["labels"][4], "day 4") + self.assertEqual(action_response[0]["data"][4], 3.0) + self.assertEqual(action_response[0]["labels"][5], "day 5") + self.assertEqual(action_response[0]["data"][5], 1.0) - self.assertEqual(action_response[1]['label'], 'sign up - previous') - self.assertEqual(action_response[1]['labels'][4], 'day 4') - self.assertEqual(action_response[1]['data'][4], 1.0) - self.assertEqual(action_response[1]['labels'][5], 'day 5') - self.assertEqual(action_response[1]['data'][5], 0.0) + self.assertEqual(action_response[1]["label"], "sign up - previous") + self.assertEqual(action_response[1]["labels"][4], "day 4") + self.assertEqual(action_response[1]["data"][4], 1.0) + self.assertEqual(action_response[1]["labels"][5], "day 5") + self.assertEqual(action_response[1]["data"][5], 0.0) self.assertTrue(self._compare_entity_response(action_response, event_response)) - def test_property_filtering(self): self._create_events() - with freeze_time('2020-01-04'): + with freeze_time("2020-01-04"): action_response = self.client.get( - '/api/action/trends/', - data={ - 'properties': jdumps({'$some_property': 'value'}), - }, + "/api/action/trends/", + data={"properties": jdumps({"$some_property": "value"}),}, ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'events': jdumps([{'id': "sign up"}, {'id': "no events"}]), - 'properties': jdumps({'$some_property': 'value'}), + "events": jdumps([{"id": "sign up"}, {"id": "no events"}]), + "properties": jdumps({"$some_property": "value"}), }, ).json() - self.assertEqual(action_response[0]['labels'][4], 'Wed. 1 January') - self.assertEqual(action_response[0]['data'][4], 1.0) - self.assertEqual(action_response[0]['labels'][5], 'Thu. 2 January') - self.assertEqual(action_response[0]['data'][5], 0) - self.assertEqual(action_response[1]['count'], 0) + self.assertEqual(action_response[0]["labels"][4], "Wed. 1 January") + self.assertEqual(action_response[0]["data"][4], 1.0) + self.assertEqual(action_response[0]["labels"][5], "Thu. 2 January") + self.assertEqual(action_response[0]["data"][5], 0) + self.assertEqual(action_response[1]["count"], 0) self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_date_filtering(self): self._create_events() - with freeze_time('2020-01-02'): - action_response = self.client.get('/api/action/trends/?date_from=2019-12-21').json() + with freeze_time("2020-01-02"): + action_response = self.client.get( + "/api/action/trends/?date_from=2019-12-21" + ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'date_from': '2019-12-21', - 'events': jdumps([{'id': "sign up"}, {'id': "no events"}]), + "date_from": "2019-12-21", + "events": jdumps([{"id": "sign up"}, {"id": "no events"}]), }, ).json() - self.assertEqual(action_response[0]['labels'][3], 'Tue. 24 December') - self.assertEqual(action_response[0]['data'][3], 1.0) - self.assertEqual(action_response[0]['data'][12], 1.0) + self.assertEqual(action_response[0]["labels"][3], "Tue. 24 December") + self.assertEqual(action_response[0]["data"][3], 1.0) + self.assertEqual(action_response[0]["data"][12], 1.0) self.assertTrue(self._compare_entity_response(action_response, event_response)) @@ -325,553 +402,782 @@ class TestTrends(TransactionBaseTest): self._create_events(use_time=True) # test minute - with freeze_time('2020-01-02'): - action_response = self.client.get('/api/action/trends/?date_from=2020-01-01&interval=minute').json() - self.assertEqual(action_response[0]['labels'][6], 'Wed. 1 January, 00:06') - self.assertEqual(action_response[0]['data'][6], 3.0) + with freeze_time("2020-01-02"): + action_response = self.client.get( + "/api/action/trends/?date_from=2020-01-01&interval=minute" + ).json() + self.assertEqual(action_response[0]["labels"][6], "Wed. 1 January, 00:06") + self.assertEqual(action_response[0]["data"][6], 3.0) # test hour - with freeze_time('2020-01-02'): - action_response = self.client.get('/api/action/trends/?date_from=2019-12-24&interval=hour').json() - self.assertEqual(action_response[0]['labels'][3], 'Tue. 24 December, 03:00') - self.assertEqual(action_response[0]['data'][3], 1.0) + with freeze_time("2020-01-02"): + action_response = self.client.get( + "/api/action/trends/?date_from=2019-12-24&interval=hour" + ).json() + self.assertEqual(action_response[0]["labels"][3], "Tue. 24 December, 03:00") + self.assertEqual(action_response[0]["data"][3], 1.0) # 217 - 24 - 1 - self.assertEqual(action_response[0]['data'][192], 3.0) + self.assertEqual(action_response[0]["data"][192], 3.0) # test week - with freeze_time('2020-01-02'): - action_response = self.client.get('/api/action/trends/?date_from=2019-11-24&interval=week').json() - self.assertEqual(action_response[0]['labels'][4], 'Sun. 22 December') - self.assertEqual(action_response[0]['data'][4], 1.0) - self.assertEqual(action_response[0]['labels'][5], 'Sun. 29 December') - self.assertEqual(action_response[0]['data'][5], 4.0) + with freeze_time("2020-01-02"): + action_response = self.client.get( + "/api/action/trends/?date_from=2019-11-24&interval=week" + ).json() + self.assertEqual(action_response[0]["labels"][4], "Sun. 22 December") + self.assertEqual(action_response[0]["data"][4], 1.0) + self.assertEqual(action_response[0]["labels"][5], "Sun. 29 December") + self.assertEqual(action_response[0]["data"][5], 4.0) # test month - with freeze_time('2020-01-02'): - action_response = self.client.get('/api/action/trends/?date_from=2019-9-24&interval=month').json() - self.assertEqual(action_response[0]['labels'][2], 'Sat. 30 November') - self.assertEqual(action_response[0]['data'][2], 1.0) - self.assertEqual(action_response[0]['labels'][3], 'Tue. 31 December') - self.assertEqual(action_response[0]['data'][3], 4.0) + with freeze_time("2020-01-02"): + action_response = self.client.get( + "/api/action/trends/?date_from=2019-9-24&interval=month" + ).json() + self.assertEqual(action_response[0]["labels"][2], "Sat. 30 November") + self.assertEqual(action_response[0]["data"][2], 1.0) + self.assertEqual(action_response[0]["labels"][3], "Tue. 31 December") + self.assertEqual(action_response[0]["data"][3], 4.0) - with freeze_time('2020-01-02 23:30'): - Event.objects.create(team=self.team, event='sign up', distinct_id='blabla') + with freeze_time("2020-01-02 23:30"): + Event.objects.create(team=self.team, event="sign up", distinct_id="blabla") # test today + hourly - with freeze_time('2020-01-02T23:31:00Z'): + with freeze_time("2020-01-02T23:31:00Z"): action_response = self.client.get( - '/api/action/trends/', - data={ - 'date_from': 'dStart', - 'interval': 'hour', - }, + "/api/action/trends/", + data={"date_from": "dStart", "interval": "hour",}, ).json() - self.assertEqual(action_response[0]['labels'][23], 'Thu. 2 January, 23:00') - self.assertEqual(action_response[0]['data'][23], 1.0) + self.assertEqual(action_response[0]["labels"][23], "Thu. 2 January, 23:00") + self.assertEqual(action_response[0]["data"][23], 1.0) def test_all_dates_filtering(self): self._create_events(use_time=True) # automatically sets first day as first day of any events - with freeze_time('2020-01-04T15:01:01Z'): - action_response = self.client.get('/api/action/trends/?date_from=all').json() + with freeze_time("2020-01-04T15:01:01Z"): + action_response = self.client.get( + "/api/action/trends/?date_from=all" + ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'date_from': 'all', - 'events': jdumps([{'id': "sign up"}, {'id': "no events"}]), + "date_from": "all", + "events": jdumps([{"id": "sign up"}, {"id": "no events"}]), }, ).json() - self.assertEqual(action_response[0]['labels'][0], 'Tue. 24 December') - self.assertEqual(action_response[0]['data'][0], 1.0) + self.assertEqual(action_response[0]["labels"][0], "Tue. 24 December") + self.assertEqual(action_response[0]["data"][0], 1.0) self.assertTrue(self._compare_entity_response(action_response, event_response)) - # test empty response - with freeze_time('2020-01-04'): - empty = self.client.get('/api/action/trends/?date_from=all&events=%s' % jdumps([{'id': 'blabla'}, {'id': 'sign up'}])).json() - self.assertEqual(empty[0]['data'][0], 0) + with freeze_time("2020-01-04"): + empty = self.client.get( + "/api/action/trends/?date_from=all&events=%s" + % jdumps([{"id": "blabla"}, {"id": "sign up"}]) + ).json() + self.assertEqual(empty[0]["data"][0], 0) def test_breakdown_filtering(self): self._create_events() # test breakdown filtering - with freeze_time('2020-01-04T13:01:01Z'): - action_response = self.client.get('/api/action/trends/?date_from=-14d&breakdown=$some_property').json() - event_response = self.client.get('/api/action/trends/?date_from=-14d&properties={}&actions=[]&display=ActionsTable&interval=day&breakdown=$some_property&events=%s' % jdumps([{'id': "sign up", "name": "sign up", "type": "events", "order": 0}, {'id': "no events"}])).json() + with freeze_time("2020-01-04T13:01:01Z"): + action_response = self.client.get( + "/api/action/trends/?date_from=-14d&breakdown=$some_property" + ).json() + event_response = self.client.get( + "/api/action/trends/?date_from=-14d&properties={}&actions=[]&display=ActionsTable&interval=day&breakdown=$some_property&events=%s" + % jdumps( + [ + { + "id": "sign up", + "name": "sign up", + "type": "events", + "order": 0, + }, + {"id": "no events"}, + ] + ) + ).json() - self.assertEqual(event_response[0]['label'], 'sign up - Other') - self.assertEqual(event_response[1]['label'], 'sign up - other_value') - self.assertEqual(event_response[2]['label'], 'sign up - value') - self.assertEqual(event_response[3]['label'], 'no events - Other') + self.assertEqual(event_response[0]["label"], "sign up - Other") + self.assertEqual(event_response[1]["label"], "sign up - other_value") + self.assertEqual(event_response[2]["label"], "sign up - value") + self.assertEqual(event_response[3]["label"], "no events - Other") - self.assertEqual(sum(event_response[0]['data']), 2) - self.assertEqual(event_response[0]['data'][4+7], 2) - self.assertEqual(event_response[0]['breakdown_value'], 'None') + self.assertEqual(sum(event_response[0]["data"]), 2) + self.assertEqual(event_response[0]["data"][4 + 7], 2) + self.assertEqual(event_response[0]["breakdown_value"], "None") - self.assertEqual(sum(event_response[1]['data']), 1) - self.assertEqual(event_response[1]['data'][5+7], 1) - self.assertEqual(event_response[1]['breakdown_value'], 'other_value') + self.assertEqual(sum(event_response[1]["data"]), 1) + self.assertEqual(event_response[1]["data"][5 + 7], 1) + self.assertEqual(event_response[1]["breakdown_value"], "other_value") self.assertTrue(self._compare_entity_response(action_response, event_response)) # check numerical breakdown - with freeze_time('2020-01-04T13:01:01Z'): - action_response = self.client.get('/api/action/trends/?date_from=-14d&breakdown=$some_numerical_prop').json() - event_response = self.client.get('/api/action/trends/?date_from=-14d&properties={}&actions=[]&display=ActionsTable&interval=day&breakdown=$some_numerical_prop&events=%s' % jdumps([{'id': "sign up", "name": "sign up", "type": "events", "order": 0}, {'id': "no events"}])).json() - self.assertEqual(event_response[0]['label'], 'sign up - Other') - self.assertEqual(event_response[0]['count'], 4.0) - self.assertEqual(event_response[1]['label'], 'sign up - 80.0') - self.assertEqual(event_response[1]['count'], 1.0) + with freeze_time("2020-01-04T13:01:01Z"): + action_response = self.client.get( + "/api/action/trends/?date_from=-14d&breakdown=$some_numerical_prop" + ).json() + event_response = self.client.get( + "/api/action/trends/?date_from=-14d&properties={}&actions=[]&display=ActionsTable&interval=day&breakdown=$some_numerical_prop&events=%s" + % jdumps( + [ + { + "id": "sign up", + "name": "sign up", + "type": "events", + "order": 0, + }, + {"id": "no events"}, + ] + ) + ).json() + self.assertEqual(event_response[0]["label"], "sign up - Other") + self.assertEqual(event_response[0]["count"], 4.0) + self.assertEqual(event_response[1]["label"], "sign up - 80.0") + self.assertEqual(event_response[1]["count"], 1.0) self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_breakdown_filtering_limit(self): self._create_breakdown_events() - with freeze_time('2020-01-04T13:01:01Z'): - action_response = self.client.get('/api/action/trends/?date_from=-14d&breakdown=$some_property').json() - event_response = self.client.get('/api/action/trends/?date_from=-14d&properties={}&actions=[]&display=ActionsTable&interval=day&breakdown=$some_property&events=%s' % jdumps([{'id': "sign up", "name": "sign up", "type": "events", "order": 0}])).json() + with freeze_time("2020-01-04T13:01:01Z"): + action_response = self.client.get( + "/api/action/trends/?date_from=-14d&breakdown=$some_property" + ).json() + event_response = self.client.get( + "/api/action/trends/?date_from=-14d&properties={}&actions=[]&display=ActionsTable&interval=day&breakdown=$some_property&events=%s" + % jdumps( + [{"id": "sign up", "name": "sign up", "type": "events", "order": 0}] + ) + ).json() self.assertEqual(len(action_response), 20) self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_action_filtering(self): sign_up_action, person = self._create_events() - with freeze_time('2020-01-04'): + with freeze_time("2020-01-04"): action_response = self.client.get( - '/api/action/trends/', - data={ - 'actions': jdumps([{'id': sign_up_action.id}]), - }, + "/api/action/trends/", + data={"actions": jdumps([{"id": sign_up_action.id}]),}, ).json() event_response = self.client.get( - '/api/action/trends/', - data={ - 'events': jdumps([{'id': "sign up"}]), - }, + "/api/action/trends/", data={"events": jdumps([{"id": "sign up"}]),}, ).json() self.assertEqual(len(action_response), 1) self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_trends_for_non_existing_action(self): - with freeze_time('2020-01-04'): - response = self.client.get('/api/action/trends/', {'actions': jdumps([{'id': 4000000}])}).json() + with freeze_time("2020-01-04"): + response = self.client.get( + "/api/action/trends/", {"actions": jdumps([{"id": 4000000}])} + ).json() self.assertEqual(len(response), 0) - with freeze_time('2020-01-04'): - response = self.client.get('/api/action/trends/', {'events': jdumps([{'id': "DNE"}])}).json() + with freeze_time("2020-01-04"): + response = self.client.get( + "/api/action/trends/", {"events": jdumps([{"id": "DNE"}])} + ).json() - self.assertEqual(response[0]['data'], [0, 0, 0, 0, 0, 0, 0, 0]) + self.assertEqual(response[0]["data"], [0, 0, 0, 0, 0, 0, 0, 0]) def test_dau_filtering(self): sign_up_action, person = self._create_events() - with freeze_time('2020-01-02'): - Person.objects.create(team=self.team, distinct_ids=['someone_else']) - Event.objects.create(team=self.team, event='sign up', distinct_id='someone_else') - with freeze_time('2020-01-04'): + with freeze_time("2020-01-02"): + Person.objects.create(team=self.team, distinct_ids=["someone_else"]) + Event.objects.create( + team=self.team, event="sign up", distinct_id="someone_else" + ) + with freeze_time("2020-01-04"): action_response = self.client.get( - '/api/action/trends/', - data={ - 'actions': jdumps([{'id': sign_up_action.id, 'math': 'dau'}]), - }, + "/api/action/trends/", + data={"actions": jdumps([{"id": sign_up_action.id, "math": "dau"}]),}, ).json() event_response = self.client.get( - '/api/action/trends/', - data={ - 'events': jdumps([{'id': "sign up", 'math': 'dau'}]), - }, + "/api/action/trends/", + data={"events": jdumps([{"id": "sign up", "math": "dau"}]),}, ).json() - self.assertEqual(action_response[0]['data'][4], 1) - self.assertEqual(action_response[0]['data'][5], 2) + self.assertEqual(action_response[0]["data"][4], 1) + self.assertEqual(action_response[0]["data"][5], 2) self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_dau_with_breakdown_filtering(self): sign_up_action, _ = self._create_events() - with freeze_time('2020-01-02'): - Event.objects.create(team=self.team, event='sign up', distinct_id='blabla', properties={"$some_property": "other_value"}) - with freeze_time('2020-01-04'): - action_response = self.client.get('/api/action/trends/?breakdown=$some_property&actions=%s' % jdumps([{'id': sign_up_action.id, 'math': 'dau'}])).json() - event_response = self.client.get('/api/action/trends/?breakdown=$some_property&events=%s' % jdumps([{'id': "sign up", 'math': 'dau'}])).json() + with freeze_time("2020-01-02"): + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="blabla", + properties={"$some_property": "other_value"}, + ) + with freeze_time("2020-01-04"): + action_response = self.client.get( + "/api/action/trends/?breakdown=$some_property&actions=%s" + % jdumps([{"id": sign_up_action.id, "math": "dau"}]) + ).json() + event_response = self.client.get( + "/api/action/trends/?breakdown=$some_property&events=%s" + % jdumps([{"id": "sign up", "math": "dau"}]) + ).json() - self.assertEqual(event_response[0]['label'], 'sign up - other_value') - self.assertEqual(event_response[1]['label'], 'sign up - value') - self.assertEqual(event_response[2]['label'], 'sign up - Other') + self.assertEqual(event_response[0]["label"], "sign up - other_value") + self.assertEqual(event_response[1]["label"], "sign up - value") + self.assertEqual(event_response[2]["label"], "sign up - Other") - self.assertEqual(sum(event_response[0]['data']), 1) - self.assertEqual(event_response[0]['data'][5], 1) + self.assertEqual(sum(event_response[0]["data"]), 1) + self.assertEqual(event_response[0]["data"][5], 1) + + self.assertEqual(sum(event_response[2]["data"]), 1) + self.assertEqual(event_response[2]["data"][4], 1) # property not defined - self.assertEqual(sum(event_response[2]['data']), 1) - self.assertEqual(event_response[2]['data'][4], 1) # property not defined - self.assertTrue(self._compare_entity_response(action_response, event_response)) def test_people_endpoint(self): sign_up_action, person = self._create_events() - person1 = Person.objects.create(team=self.team, distinct_ids=['person1']) - Person.objects.create(team=self.team, distinct_ids=['person2']) - Event.objects.create(team=self.team, event='sign up', distinct_id='person1', timestamp='2020-01-04T12:00:00Z') - Event.objects.create(team=self.team, event='sign up', distinct_id='person2', timestamp='2020-01-05T12:00:00Z') + person1 = Person.objects.create(team=self.team, distinct_ids=["person1"]) + Person.objects.create(team=self.team, distinct_ids=["person2"]) + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person1", + timestamp="2020-01-04T12:00:00Z", + ) + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person2", + timestamp="2020-01-05T12:00:00Z", + ) # test people action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'date_from': '2020-01-04', - 'date_to': '2020-01-04', - 'type': 'actions', - 'entityId': sign_up_action.id, + "date_from": "2020-01-04", + "date_to": "2020-01-04", + "type": "actions", + "entityId": sign_up_action.id, }, ).json() event_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'date_from': '2020-01-04', - 'date_to': '2020-01-04', - 'type': 'events', - 'entityId': 'sign up', + "date_from": "2020-01-04", + "date_to": "2020-01-04", + "type": "events", + "entityId": "sign up", }, ).json() - self.assertEqual(action_response[0]['people'][0]['id'], person1.pk) - self.assertTrue(self._compare_entity_response(action_response, event_response, remove=[])) + self.assertEqual(action_response["results"][0]["people"][0]["id"], person1.pk) + self.assertTrue( + self._compare_entity_response( + action_response["results"], event_response["results"], remove=[] + ) + ) + + def test_people_endpoint_paginated(self): + + for index in range(0, 150): + Person.objects.create(team=self.team, distinct_ids=["person" + str(index)]) + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person" + str(index), + timestamp="2020-01-04T12:00:00Z", + ) + + event_response = self.client.get( + "/api/action/people/", + data={ + "date_from": "2020-01-04", + "date_to": "2020-01-04", + "type": "events", + "entityId": "sign up", + }, + ).json() + self.assertEqual(len(event_response["results"][0]["people"]), 100) + event_response_next = self.client.get(event_response["next"]).json() + self.assertEqual(len(event_response_next["results"][0]["people"]), 50) def test_people_endpoint_with_intervals(self): sign_up_action, person = self._create_events() - person1 = Person.objects.create(team=self.team, distinct_ids=['person1']) - person2 = Person.objects.create(team=self.team, distinct_ids=['person2']) - person3 = Person.objects.create(team=self.team, distinct_ids=['person3']) - person4 = Person.objects.create(team=self.team, distinct_ids=['person4']) - person5 = Person.objects.create(team=self.team, distinct_ids=['person5']) - person6 = Person.objects.create(team=self.team, distinct_ids=['person6']) - person7 = Person.objects.create(team=self.team, distinct_ids=['person7']) + person1 = Person.objects.create(team=self.team, distinct_ids=["person1"]) + person2 = Person.objects.create(team=self.team, distinct_ids=["person2"]) + person3 = Person.objects.create(team=self.team, distinct_ids=["person3"]) + person4 = Person.objects.create(team=self.team, distinct_ids=["person4"]) + person5 = Person.objects.create(team=self.team, distinct_ids=["person5"]) + person6 = Person.objects.create(team=self.team, distinct_ids=["person6"]) + person7 = Person.objects.create(team=self.team, distinct_ids=["person7"]) # solo - Event.objects.create(team=self.team, event='sign up', distinct_id='person1', timestamp='2020-01-04T14:10:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person1", + timestamp="2020-01-04T14:10:00Z", + ) # group by hour - Event.objects.create(team=self.team, event='sign up', distinct_id='person2', timestamp='2020-01-04T16:30:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person2", + timestamp="2020-01-04T16:30:00Z", + ) # group by hour - Event.objects.create(team=self.team, event='sign up', distinct_id='person3', timestamp='2020-01-04T16:50:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person3", + timestamp="2020-01-04T16:50:00Z", + ) # group by min - Event.objects.create(team=self.team, event='sign up', distinct_id='person4', timestamp='2020-01-04T19:20:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person4", + timestamp="2020-01-04T19:20:00Z", + ) # group by min - Event.objects.create(team=self.team, event='sign up', distinct_id='person5', timestamp='2020-01-04T19:20:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person5", + timestamp="2020-01-04T19:20:00Z", + ) # group by week and month - Event.objects.create(team=self.team, event='sign up', distinct_id='person6', timestamp='2019-11-05T16:30:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person6", + timestamp="2019-11-05T16:30:00Z", + ) # group by week and month - Event.objects.create(team=self.team, event='sign up', distinct_id='person7', timestamp='2019-11-07T16:50:00Z') + Event.objects.create( + team=self.team, + event="sign up", + distinct_id="person7", + timestamp="2019-11-07T16:50:00Z", + ) # check solo hour action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'hour', - 'date_from': '2020-01-04 14:00:00', - 'date_to': '2020-01-04 14:00:00', - 'type': 'actions', - 'entityId': sign_up_action.id, + "interval": "hour", + "date_from": "2020-01-04 14:00:00", + "date_to": "2020-01-04 14:00:00", + "type": "actions", + "entityId": sign_up_action.id, }, ).json() event_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'hour', - 'date_from': '2020-01-04 14:00:00', - 'date_to': '2020-01-04 14:00:00', - 'type': 'events', - 'entityId': 'sign up', + "interval": "hour", + "date_from": "2020-01-04 14:00:00", + "date_to": "2020-01-04 14:00:00", + "type": "events", + "entityId": "sign up", }, ).json() - self.assertEqual(action_response[0]['people'][0]['id'], person1.pk) - self.assertEqual(len(action_response[0]['people']), 1) - self.assertTrue(self._compare_entity_response(action_response, event_response, remove=[])) + self.assertEqual(action_response["results"][0]["people"][0]["id"], person1.pk) + self.assertEqual(len(action_response["results"][0]["people"]), 1) + self.assertTrue( + self._compare_entity_response( + action_response["results"], event_response["results"], remove=[] + ) + ) # check grouped hour hour_grouped_action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'hour', - 'date_from': '2020-01-04 16:00:00', - 'date_to': '2020-01-04 16:00:00', - 'type': 'actions', - 'entityId': sign_up_action.id, + "interval": "hour", + "date_from": "2020-01-04 16:00:00", + "date_to": "2020-01-04 16:00:00", + "type": "actions", + "entityId": sign_up_action.id, }, ).json() hour_grouped_grevent_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'hour', - 'date_from': '2020-01-04 16:00:00', - 'date_to': '2020-01-04 16:00:00', - 'type': 'events', - 'entityId': 'sign up', + "interval": "hour", + "date_from": "2020-01-04 16:00:00", + "date_to": "2020-01-04 16:00:00", + "type": "events", + "entityId": "sign up", }, ).json() - self.assertEqual(hour_grouped_action_response[0]['people'][0]['id'], person2.pk) - self.assertEqual(hour_grouped_action_response[0]['people'][1]['id'], person3.pk) - self.assertEqual(len(hour_grouped_action_response[0]['people']), 2) - self.assertTrue(self._compare_entity_response( - hour_grouped_action_response, - hour_grouped_grevent_response, - remove=[], - )) + self.assertEqual( + hour_grouped_action_response["results"][0]["people"][0]["id"], person2.pk + ) + self.assertEqual( + hour_grouped_action_response["results"][0]["people"][1]["id"], person3.pk + ) + self.assertEqual(len(hour_grouped_action_response["results"][0]["people"]), 2) + self.assertTrue( + self._compare_entity_response( + hour_grouped_action_response["results"], + hour_grouped_grevent_response["results"], + remove=[], + ) + ) # check grouped minute min_grouped_action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'hour', - 'date_from': '2020-01-04 19:20:00', - 'date_to': '2020-01-04 19:20:00', - 'type': 'actions', - 'entityId': sign_up_action.id, + "interval": "hour", + "date_from": "2020-01-04 19:20:00", + "date_to": "2020-01-04 19:20:00", + "type": "actions", + "entityId": sign_up_action.id, }, ).json() min_grouped_grevent_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'hour', - 'date_from': '2020-01-04 19:20:00', - 'date_to': '2020-01-04 19:20:00', - 'type': 'events', - 'entityId': 'sign up', + "interval": "hour", + "date_from": "2020-01-04 19:20:00", + "date_to": "2020-01-04 19:20:00", + "type": "events", + "entityId": "sign up", }, ).json() - self.assertEqual(min_grouped_action_response[0]['people'][0]['id'], person4.pk) - self.assertEqual(min_grouped_action_response[0]['people'][1]['id'], person5.pk) - self.assertEqual(len(min_grouped_action_response[0]['people']), 2) - self.assertTrue(self._compare_entity_response( - min_grouped_action_response, - min_grouped_grevent_response, - remove=[], - )) + self.assertEqual( + min_grouped_action_response["results"][0]["people"][0]["id"], person4.pk + ) + self.assertEqual( + min_grouped_action_response["results"][0]["people"][1]["id"], person5.pk + ) + self.assertEqual(len(min_grouped_action_response["results"][0]["people"]), 2) + self.assertTrue( + self._compare_entity_response( + min_grouped_action_response["results"], + min_grouped_grevent_response["results"], + remove=[], + ) + ) # check grouped week week_grouped_action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'week', - 'date_from': '2019-11-01', - 'date_to': '2019-11-01', - 'type': 'actions', - 'entityId': sign_up_action.id, + "interval": "week", + "date_from": "2019-11-01", + "date_to": "2019-11-01", + "type": "actions", + "entityId": sign_up_action.id, }, ).json() week_grouped_grevent_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'week', - 'date_from': '2019-11-01', - 'date_to': '2019-11-01', - 'type': 'events', - 'entityId': 'sign up', + "interval": "week", + "date_from": "2019-11-01", + "date_to": "2019-11-01", + "type": "events", + "entityId": "sign up", }, ).json() - self.assertEqual(week_grouped_action_response[0]['people'][0]['id'], person6.pk) - self.assertEqual(week_grouped_action_response[0]['people'][1]['id'], person7.pk) - self.assertEqual(len(week_grouped_action_response[0]['people']), 2) - self.assertTrue(self._compare_entity_response( - week_grouped_action_response, - week_grouped_grevent_response, - remove=[], - )) + self.assertEqual( + week_grouped_action_response["results"][0]["people"][0]["id"], person6.pk + ) + self.assertEqual( + week_grouped_action_response["results"][0]["people"][1]["id"], person7.pk + ) + self.assertEqual(len(week_grouped_action_response["results"][0]["people"]), 2) + self.assertTrue( + self._compare_entity_response( + week_grouped_action_response["results"], + week_grouped_grevent_response["results"], + remove=[], + ) + ) # check grouped month month_group_action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'month', - 'date_from': '2019-11-01', - 'date_to': '2019-11-01', - 'type': 'actions', - 'entityId': sign_up_action.id, + "interval": "month", + "date_from": "2019-11-01", + "date_to": "2019-11-01", + "type": "actions", + "entityId": sign_up_action.id, }, ).json() month_group_grevent_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'interval': 'month', - 'date_from': '2019-11-01', - 'date_to': '2019-11-01', - 'type': 'events', - 'entityId': 'sign up', + "interval": "month", + "date_from": "2019-11-01", + "date_to": "2019-11-01", + "type": "events", + "entityId": "sign up", }, ).json() - self.assertEqual(month_group_action_response[0]['people'][0]['id'], person6.pk) - self.assertEqual(month_group_action_response[0]['people'][1]['id'], person7.pk) - self.assertEqual(len(month_group_action_response[0]['people']), 2) - self.assertTrue(self._compare_entity_response( - month_group_action_response, - month_group_grevent_response, - remove=[], - )) + self.assertEqual( + month_group_action_response["results"][0]["people"][0]["id"], person6.pk + ) + self.assertEqual( + month_group_action_response["results"][0]["people"][1]["id"], person7.pk + ) + self.assertEqual(len(month_group_action_response["results"][0]["people"]), 2) + self.assertTrue( + self._compare_entity_response( + month_group_action_response["results"], + month_group_grevent_response["results"], + remove=[], + ) + ) def _create_multiple_people(self): - person1 = Person.objects.create(team=self.team, distinct_ids=['person1'], properties={'name': 'person1'}) - Event.objects.create(team=self.team, event='watched movie', distinct_id='person1', timestamp='2020-01-01T12:00:00Z') + person1 = Person.objects.create( + team=self.team, distinct_ids=["person1"], properties={"name": "person1"} + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person1", + timestamp="2020-01-01T12:00:00Z", + ) - person2 = Person.objects.create(team=self.team, distinct_ids=['person2'], properties={'name': 'person2'}) - Event.objects.create(team=self.team, event='watched movie', distinct_id='person2', timestamp='2020-01-01T12:00:00Z') - Event.objects.create(team=self.team, event='watched movie', distinct_id='person2', timestamp='2020-01-02T12:00:00Z') + person2 = Person.objects.create( + team=self.team, distinct_ids=["person2"], properties={"name": "person2"} + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person2", + timestamp="2020-01-01T12:00:00Z", + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person2", + timestamp="2020-01-02T12:00:00Z", + ) # same day - Event.objects.create(team=self.team, event='watched movie', distinct_id='person2', timestamp='2020-01-02T12:00:00Z') + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person2", + timestamp="2020-01-02T12:00:00Z", + ) - person3 = Person.objects.create(team=self.team, distinct_ids=['person3'], properties={'name': 'person3'}) - Event.objects.create(team=self.team, event='watched movie', distinct_id='person3', timestamp='2020-01-01T12:00:00Z') - Event.objects.create(team=self.team, event='watched movie', distinct_id='person3', timestamp='2020-01-02T12:00:00Z') - Event.objects.create(team=self.team, event='watched movie', distinct_id='person3', timestamp='2020-01-03T12:00:00Z') + person3 = Person.objects.create( + team=self.team, distinct_ids=["person3"], properties={"name": "person3"} + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person3", + timestamp="2020-01-01T12:00:00Z", + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person3", + timestamp="2020-01-02T12:00:00Z", + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person3", + timestamp="2020-01-03T12:00:00Z", + ) - person4 =Person.objects.create(team=self.team, distinct_ids=['person4'], properties={'name': 'person4'}) - Event.objects.create(team=self.team, event='watched movie', distinct_id='person4', timestamp='2020-01-05T12:00:00Z') + person4 = Person.objects.create( + team=self.team, distinct_ids=["person4"], properties={"name": "person4"} + ) + Event.objects.create( + team=self.team, + event="watched movie", + distinct_id="person4", + timestamp="2020-01-05T12:00:00Z", + ) return (person1, person2, person3, person4) def test_stickiness(self): person1 = self._create_multiple_people()[0] watched_movie = Action.objects.create(team=self.team) - ActionStep.objects.create(action=watched_movie, event='watched movie') + ActionStep.objects.create(action=watched_movie, event="watched movie") watched_movie.calculate_events() - with freeze_time('2020-01-08T13:01:01Z'): + with freeze_time("2020-01-08T13:01:01Z"): action_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'shown_as': 'Stickiness', - 'actions': jdumps([{'id': watched_movie.id}]), + "shown_as": "Stickiness", + "actions": jdumps([{"id": watched_movie.id}]), }, ).json() event_response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'shown_as': 'Stickiness', - 'date_from': '2020-01-01', - 'date_to': '2020-01-08', - 'events': jdumps([{'id': "watched movie"}]), + "shown_as": "Stickiness", + "date_from": "2020-01-01", + "date_to": "2020-01-08", + "events": jdumps([{"id": "watched movie"}]), }, ).json() - self.assertEqual(action_response[0]['count'], 4) - self.assertEqual(action_response[0]['labels'][0], '1 day') - self.assertEqual(action_response[0]['data'][0], 2) - self.assertEqual(action_response[0]['labels'][1], '2 days') - self.assertEqual(action_response[0]['data'][1], 1) - self.assertEqual(action_response[0]['labels'][2], '3 days') - self.assertEqual(action_response[0]['data'][2], 1) - self.assertEqual(action_response[0]['labels'][6], '7 days') - self.assertEqual(action_response[0]['data'][6], 0) + self.assertEqual(action_response[0]["count"], 4) + self.assertEqual(action_response[0]["labels"][0], "1 day") + self.assertEqual(action_response[0]["data"][0], 2) + self.assertEqual(action_response[0]["labels"][1], "2 days") + self.assertEqual(action_response[0]["data"][1], 1) + self.assertEqual(action_response[0]["labels"][2], "3 days") + self.assertEqual(action_response[0]["data"][2], 1) + self.assertEqual(action_response[0]["labels"][6], "7 days") + self.assertEqual(action_response[0]["data"][6], 0) self.assertTrue(self._compare_entity_response(action_response, event_response)) # test people action_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'shown_as': 'Stickiness', - 'stickiness_days': 1, - 'date_from': '2020-01-01', - 'date_to': '2020-01-07', - 'type': 'actions', - 'entityId': watched_movie.id, + "shown_as": "Stickiness", + "stickiness_days": 1, + "date_from": "2020-01-01", + "date_to": "2020-01-07", + "type": "actions", + "entityId": watched_movie.id, }, ).json() event_response = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'shown_as': 'Stickiness', - 'stickiness_days': 1, - 'date_from': '2020-01-01', - 'date_to': '2020-01-07', - 'type': 'events', - 'entityId': 'watched movie', + "shown_as": "Stickiness", + "stickiness_days": 1, + "date_from": "2020-01-01", + "date_to": "2020-01-07", + "type": "events", + "entityId": "watched movie", }, ).json() - self.assertEqual(action_response[0]['people'][0]['id'], person1.pk) + self.assertEqual(action_response["results"][0]["people"][0]["id"], person1.pk) - self.assertTrue(self._compare_entity_response(action_response, event_response, remove=[])) + self.assertTrue( + self._compare_entity_response( + action_response["results"], event_response["results"], remove=[] + ) + ) # test all time response = self.client.get( - '/api/action/trends/', + "/api/action/trends/", data={ - 'shown_as': 'Stickiness', - 'date_from': 'all', - 'date_to': '2020-01-07', - 'events': jdumps([{'id': 'watched_movie'}]), + "shown_as": "Stickiness", + "date_from": "all", + "date_to": "2020-01-07", + "events": jdumps([{"id": "watched_movie"}]), }, ).json() - self.assertEqual(len(response[0]['data']), 7) + self.assertEqual(len(response[0]["data"]), 7) def test_breakdown_by_cohort(self): - person1, person2, person3, person4 = self._create_multiple_people() - cohort = Cohort.objects.create(name='cohort1', team=self.team, groups=[ - {'properties': {'name': 'person1'}} - ]) - cohort2 = Cohort.objects.create(name='cohort2', team=self.team, groups=[ - {'properties': {'name': 'person2'}} - ]) - cohort3 = Cohort.objects.create(name='cohort3', team=self.team, groups=[ - {'properties': {'name': 'person1'}}, - {'properties': {'name': 'person2'}}, - ]) + person1, person2, person3, person4 = self._create_multiple_people() + cohort = Cohort.objects.create( + name="cohort1", team=self.team, groups=[{"properties": {"name": "person1"}}] + ) + cohort2 = Cohort.objects.create( + name="cohort2", team=self.team, groups=[{"properties": {"name": "person2"}}] + ) + cohort3 = Cohort.objects.create( + name="cohort3", + team=self.team, + groups=[ + {"properties": {"name": "person1"}}, + {"properties": {"name": "person2"}}, + ], + ) cohort.calculate_people() cohort2.calculate_people() cohort3.calculate_people() - action = Action.objects.create(name='watched movie', team=self.team) - ActionStep.objects.create(action=action, event='watched movie') + action = Action.objects.create(name="watched movie", team=self.team) + ActionStep.objects.create(action=action, event="watched movie") action.calculate_events() - with freeze_time('2020-01-04T13:01:01Z'): - event_response = self.client.get('/api/action/trends/?date_from=-14d&breakdown=%s&breakdown_type=cohort&events=%s' % (jdumps([cohort.pk, cohort2.pk, cohort3.pk, 'all']), jdumps([{'id': "watched movie", "name": "watched movie", "type": "events", "order": 0}]))).json() - action_response = self.client.get('/api/action/trends/?date_from=-14d&breakdown=%s&breakdown_type=cohort&actions=%s' % (jdumps([cohort.pk, cohort2.pk, cohort3.pk, 'all']), jdumps([{'id': action.pk, "type": "actions", "order": 0}]))).json() + with freeze_time("2020-01-04T13:01:01Z"): + event_response = self.client.get( + "/api/action/trends/?date_from=-14d&breakdown=%s&breakdown_type=cohort&events=%s" + % ( + jdumps([cohort.pk, cohort2.pk, cohort3.pk, "all"]), + jdumps( + [ + { + "id": "watched movie", + "name": "watched movie", + "type": "events", + "order": 0, + } + ] + ), + ) + ).json() + action_response = self.client.get( + "/api/action/trends/?date_from=-14d&breakdown=%s&breakdown_type=cohort&actions=%s" + % ( + jdumps([cohort.pk, cohort2.pk, cohort3.pk, "all"]), + jdumps([{"id": action.pk, "type": "actions", "order": 0}]), + ) + ).json() - self.assertEqual(event_response[0]['label'], 'watched movie - cohort1') - self.assertEqual(event_response[1]['label'], 'watched movie - cohort2') - self.assertEqual(event_response[2]['label'], 'watched movie - cohort3') - self.assertEqual(event_response[3]['label'], 'watched movie - all users') + self.assertEqual(event_response[0]["label"], "watched movie - cohort1") + self.assertEqual(event_response[1]["label"], "watched movie - cohort2") + self.assertEqual(event_response[2]["label"], "watched movie - cohort3") + self.assertEqual(event_response[3]["label"], "watched movie - all users") - self.assertEqual(sum(event_response[0]['data']), 1) - self.assertEqual(event_response[0]['breakdown_value'], cohort.pk) + self.assertEqual(sum(event_response[0]["data"]), 1) + self.assertEqual(event_response[0]["breakdown_value"], cohort.pk) - self.assertEqual(sum(event_response[1]['data']), 3) - self.assertEqual(event_response[1]['breakdown_value'], cohort2.pk) + self.assertEqual(sum(event_response[1]["data"]), 3) + self.assertEqual(event_response[1]["breakdown_value"], cohort2.pk) - self.assertEqual(sum(event_response[2]['data']), 4) - self.assertEqual(event_response[2]['breakdown_value'], cohort3.pk) + self.assertEqual(sum(event_response[2]["data"]), 4) + self.assertEqual(event_response[2]["breakdown_value"], cohort3.pk) - self.assertEqual(sum(event_response[3]['data']), 7) - self.assertEqual(event_response[3]['breakdown_value'], 'all') + self.assertEqual(sum(event_response[3]["data"]), 7) + self.assertEqual(event_response[3]["breakdown_value"], "all") - self.assertTrue(self._compare_entity_response( - event_response, - action_response, - )) + self.assertTrue(self._compare_entity_response(event_response, action_response,)) people = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'date_from': '2020-01-01', - 'date_to': '2020-01-07', - 'type': 'events', - 'entityId': 'watched movie', - 'breakdown_type': 'cohort', - 'breakdown_value': cohort.pk, - 'breakdown': [cohort.pk] # this shouldn't do anything + "date_from": "2020-01-01", + "date_to": "2020-01-07", + "type": "events", + "entityId": "watched movie", + "breakdown_type": "cohort", + "breakdown_value": cohort.pk, + "breakdown": [cohort.pk], # this shouldn't do anything }, ).json() - self.assertEqual(len(people[0]['people']), 1) - self.assertEqual(people[0]['people'][0]['id'], person1.pk) + self.assertEqual(len(people["results"][0]["people"]), 1) + self.assertEqual(people["results"][0]["people"][0]["id"], person1.pk) # all people people = self.client.get( - '/api/action/people/', + "/api/action/people/", data={ - 'date_from': '2020-01-01', - 'date_to': '2020-01-07', - 'type': 'events', - 'entityId': 'watched movie', - 'breakdown_type': 'cohort', - 'breakdown_value': 'all', - 'breakdown': [cohort.pk] + "date_from": "2020-01-01", + "date_to": "2020-01-07", + "type": "events", + "entityId": "watched movie", + "breakdown_type": "cohort", + "breakdown_value": "all", + "breakdown": [cohort.pk], }, ).json() - self.assertEqual(len(people[0]['people']), 4) - self.assertEqual(people[0]['people'][0]['id'], person1.pk) \ No newline at end of file + self.assertEqual(len(people["results"][0]["people"]), 4) + self.assertEqual(people["results"][0]["people"][0]["id"], person1.pk) diff --git a/posthog/decorators.py b/posthog/decorators.py index bdd923b53bb..ec7641eeb65 100644 --- a/posthog/decorators.py +++ b/posthog/decorators.py @@ -6,18 +6,20 @@ import json from posthog.celery import update_cache_item from datetime import datetime + def generate_cache_key(obj): stringified = json.dumps(obj) return hashlib.md5(stringified.encode("utf-8")).hexdigest() -TRENDS_ENDPOINT = 'Trends' -FUNNEL_ENDPOINT = 'Funnel' +TRENDS_ENDPOINT = "Trends" +FUNNEL_ENDPOINT = "Funnel" + def cached_function(cache_type: str, expiry=30): def inner_decorator(f): def wrapper(*args, **kw): - cache_key = '' + cache_key = "" _expiry = expiry # prepare caching params @@ -30,28 +32,34 @@ def cached_function(cache_type: str, expiry=30): if cache_type == TRENDS_ENDPOINT: request = args[1] - filter = Filter(request=request) + filter = Filter(request=request) params = request.GET.dict() - refresh = params.pop('refresh', None) + refresh = params.pop("refresh", None) team = request.user.team_set.get() - cache_key = generate_cache_key(json.dumps(params) + '_' + str(team.pk)) - payload = {'filter': filter.toJSON(), 'params': params, 'team_id': team.pk} + cache_key = generate_cache_key(json.dumps(params) + "_" + str(team.pk)) + payload = { + "filter": filter.toJSON(), + "params": params, + "team_id": team.pk, + } elif cache_type == FUNNEL_ENDPOINT: request = args[1] pk = args[2] params = request.GET.dict() - refresh = params.pop('refresh', None) + refresh = params.pop("refresh", None) team = request.user.team_set.get() - cache_key = generate_cache_key(str(pk) + '_' + str(team.pk)) - payload = {'pk': pk, 'params': params, 'team_id': team.pk} + cache_key = generate_cache_key(str(pk) + "_" + str(team.pk)) + payload = {"pk": pk, "params": params, "team_id": team.pk} - if params and payload and params.get('from_dashboard'): #cache for 30 minutes if dashboard item - cache_key = cache_key + '_' + 'dashboard' + if ( + params and payload and params.get("from_dashboard") + ): # cache for 30 minutes if dashboard item + cache_key = cache_key + "_" + "dashboard" _expiry = 900 - dashboard_item_id = params.get('from_dashboard') - payload.update({'dashboard_id': dashboard_item_id}) - - cache_key = cache_key + '_' + cache_type + dashboard_item_id = params.get("from_dashboard") + payload.update({"dashboard_id": dashboard_item_id}) + + cache_key = cache_key + "_" + cache_type if refresh and dashboard_item_id: dashboard_item = DashboardItem.objects.filter(pk=dashboard_item_id) @@ -62,16 +70,27 @@ def cached_function(cache_type: str, expiry=30): # return result if cached cached_result = cache.get(cache_key) - if cached_result : - return cached_result['result'] + if cached_result: + return cached_result["result"] # call wrapped function result = f(*args, **kw) # cache new data using if result and payload: - cache.set(cache_key, {'result':result, 'details': payload, 'type': cache_type, 'last_accessed': datetime.now()}, _expiry) + cache.set( + cache_key, + { + "result": result, + "details": payload, + "type": cache_type, + "last_accessed": datetime.now(), + }, + _expiry, + ) return result + return wrapper - return inner_decorator \ No newline at end of file + + return inner_decorator diff --git a/webpack.config.js b/webpack.config.js index d4053ef5e7d..07cb175df06 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -34,9 +34,11 @@ function createEntry(entry) { filename: '[name].js', chunkFilename: '[name].[contenthash].js', publicPath: - process.env.NODE_ENV === 'production' ? '/static/' - : process.env.IS_PORTER ? `https://${process.env.PORTER_WEBPACK_HOST}/static/` - : `http${process.env.LOCAL_HTTPS ? 's' : ''}://${webpackDevServerHost}:8234/static/` + process.env.NODE_ENV === 'production' + ? '/static/' + : process.env.IS_PORTER + ? `https://${process.env.PORTER_WEBPACK_HOST}/static/` + : `http${process.env.LOCAL_HTTPS ? 's' : ''}://${webpackDevServerHost}:8234/static/`, }, resolve: { alias: { @@ -147,15 +149,12 @@ function createEntry(entry) { hot: true, host: webpackDevServerHost, port: 8234, - public: (process.env.IS_PORTER ? - `https://${process.env.PORTER_WEBPACK_HOST}` - : `http${process.env.LOCAL_HTTPS ? 's' : ''}://${webpackDevServerHost}:8234`), - allowedHosts: (process.env.IS_PORTER ? - [ - `${process.env.PORTER_WEBPACK_HOST}`, - `${process.env.PORTER_SERVER_HOST}` - ] - : []), + public: process.env.IS_PORTER + ? `https://${process.env.PORTER_WEBPACK_HOST}` + : `http${process.env.LOCAL_HTTPS ? 's' : ''}://${webpackDevServerHost}:8234`, + allowedHosts: process.env.IS_PORTER + ? [`${process.env.PORTER_WEBPACK_HOST}`, `${process.env.PORTER_SERVER_HOST}`] + : [], headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*',