diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 7688ac0..f7a6715 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -5,6 +5,7 @@ from rest_framework.fields import empty from backend.models import Match, Trip, User +from backend.usage import report_object_created class BaseSerializer(serializers.ModelSerializer): @@ -17,6 +18,12 @@ def get_id(self, obj): """ return obj.transactional_id if hasattr(obj, "transactional_id") else obj.id + def create(self, validated_data): + instance = super().create(validated_data) + if "request" in self.context: + report_object_created(self.context["request"].user, instance) + return instance + class ReadOnlySerializer(BaseSerializer): def create(self, validated_data): diff --git a/backend/api/views.py b/backend/api/views.py index bfe6d2a..3a0feac 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -21,6 +21,7 @@ UserUpdateSerializer, ) from backend.models import Match, Team, Trip, User +from backend.usage import report_user_signed_up class BaseModelViewSet(ModelViewSet): @@ -125,9 +126,13 @@ def social_create_user( # TODO: whereintheworld is intended for internal use only just yet, multitenancy NOT YET supported # team must be assigned based on email's TLD. + user = User.objects.create(email=email, first_name=name, team=Team.objects.first(), avatar_url=avatar_url) + + report_user_signed_up(user) + return { "is_new": True, - "user": User.objects.create(email=email, first_name=name, team=Team.objects.first(), avatar_url=avatar_url), + "user": user, } diff --git a/backend/apps.py b/backend/apps.py index 9a1ccb0..5706902 100644 --- a/backend/apps.py +++ b/backend/apps.py @@ -1,3 +1,4 @@ +import posthog from django.apps import AppConfig @@ -7,3 +8,5 @@ class WhereInTheWorldConfig(AppConfig): def ready(self): import backend.signals # noqa: F401 + + posthog.api_key = "phc_EJzNlXWFR9fCwwv9hOTMZooUs0UnnlLLwla07KKXvOi" diff --git a/backend/models.py b/backend/models.py index c643c81..c2e7f06 100644 --- a/backend/models.py +++ b/backend/models.py @@ -1,5 +1,5 @@ import datetime as dt -from typing import List +from typing import Any, Dict, List from cities.models import City from django.conf import settings @@ -98,6 +98,14 @@ def clean(self) -> None: if self.start > self.end: raise ValidationError({"end": "Must be before start."}) + def analytics_props(self) -> Dict[str, Any]: + return { + "has_notes": len(self.notes) > 0, + "start": self.start, + "end": self.end, + "country": self.city.country.code, + } + def compute_matches(self): insert_statements = [] diff --git a/backend/usage.py b/backend/usage.py new file mode 100644 index 0000000..3e326c1 --- /dev/null +++ b/backend/usage.py @@ -0,0 +1,20 @@ +import posthog + + +def report_user_signed_up(user): + posthog.identify(user.transactional_id, properties={"email": user.email}) + posthog.capture( + user.transactional_id, + "user signed up", + groups={"team": user.team.transactional_id}, + ) + + +def report_object_created(user, instance): + properties = instance.analytics_props() if hasattr(instance, "analytics_props") else {} + posthog.capture( + user.transactional_id, + f"{instance.__class__.__name__.lower()} created", + properties=properties, + groups={"team": user.team.transactional_id}, + ) diff --git a/package.json b/package.json index 8793fdf..29b450f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "kea-router": "^1.0.6", "kea-typegen": "^1.1.5", "next": "11.1.2", + "posthog-js": "^1.16.8", "prettier": "^2.3.2", "rc-slider": "^9.7.2", "react": "^17.0.2", diff --git a/requirements.txt b/requirements.txt index da0d5e7..7e00295 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ django-extensions==3.1.3 djangorestframework==3.12.4 drf-exceptions-hog==0.1.0 gunicorn==20.1.0 +posthog==1.4.4 psycopg2-binary==2.9.2 social-auth-app-django==5.0.0 whitenoise==5.3.0 \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 2f58528..d195173 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,6 +5,9 @@ import { resetContext, Provider } from 'kea' import { loadersPlugin } from 'kea-loaders' import { routerPlugin } from 'kea-router' import { App } from './App' +import posthog from 'posthog-js' + +posthog.init('phc_EJzNlXWFR9fCwwv9hOTMZooUs0UnnlLLwla07KKXvOi', { api_host: 'https://app.posthog.com' }) resetContext({ createStore: { diff --git a/src/logics/authLogic.ts b/src/logics/authLogic.ts index 89d8159..85acd7d 100644 --- a/src/logics/authLogic.ts +++ b/src/logics/authLogic.ts @@ -3,6 +3,9 @@ import { router } from 'kea-router' import api from 'lib/api' import { UserType } from '~/types' import type { authLogicType } from './authLogicType' +import posthog from 'posthog-js' + +const ENV = window.location.href.indexOf('localhost') >= 0 ? 'development' : 'production' interface UserUpdatePayload { home_city: number @@ -26,6 +29,8 @@ export const authLogic = kea>({ if (user && !user.home_city) { router.actions.push('/welcome') } + posthog.identify(user.id, { email: ENV === 'production' ? user.email : `dev_${user.email}`, env: ENV }) + posthog.register({ env: ENV }) }, }, events: ({ actions }) => ({ diff --git a/yarn.lock b/yarn.lock index 66e2ff7..107f11c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1584,6 +1584,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== +"@sentry/types@^6.11.0": + version "6.16.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.16.1.tgz#4917607115b30315757c2cf84f80bac5100b8ac0" + integrity sha512-Wh354g30UsJ5kYJbercektGX4ZMc9MHU++1NjeN2bTMnbofEcpUDWIiKeulZEY65IC1iU+1zRQQgtYO+/hgCUQ== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -4824,6 +4829,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.4.1: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -8081,6 +8091,15 @@ postcss@^8.1.6, postcss@^8.2.15, postcss@^8.3.5, postcss@^8.4.4: picocolors "^1.0.0" source-map-js "^1.0.1" +posthog-js@^1.16.8: + version "1.16.8" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.16.8.tgz#bdc8881f1a5f267b97b9e7042130abf37355d8bf" + integrity sha512-Rfh9Jhtt4HDq0uVhFfYFtZ3uPSrn0oA3ua7/iIDMx+vrFxEXeL0DQwaPZYSEfN8C1wTxK/6AOX8sVZcuBCdPqw== + dependencies: + "@sentry/types" "^6.11.0" + fflate "^0.4.1" + rrweb-snapshot "^1.1.7" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -8858,6 +8877,11 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" +rrweb-snapshot@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.11.tgz#2eebe16e21c2edde287d07b090934f3c398d476f" + integrity sha512-4FRkBQCS4T++FvGurSonBMMYo9XEClTe7sbfoiSrA38mutuJdx0gakFPbtnvLdDmM41a4vEGIB7Hs4JMQnS1zA== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"