Skip to content

Commit eaf517c

Browse files
feat: Publish onboarding events (#1147)
1 parent 659b4ec commit eaf517c

File tree

5 files changed

+166
-3
lines changed

5 files changed

+166
-3
lines changed

codecov/settings_base.py

+2
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@
433433
"setup", "stripe", "payment_method_configuration_id", default=None
434434
)
435435

436+
AMPLITUDE_API_KEY = os.environ.get("AMPLITUDE_API_KEY", None)
437+
436438
# Allows to do migrations from another module
437439
MIGRATION_MODULES = {
438440
"codecov_auth": "shared.django_apps.codecov_auth.migrations",

codecov_auth/tests/unit/views/test_base.py

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime, timedelta, timezone
2-
from unittest.mock import Mock, patch
2+
from unittest.mock import Mock, call, patch
33

44
import pytest
55
from django.conf import settings
@@ -174,6 +174,27 @@ def test_get_or_create_calls_analytics_user_signed_up_when_owner_created(
174174
)
175175
user_signed_up_mock.assert_called_once()
176176

177+
@patch("shared.events.amplitude.AmplitudeEventPublisher.publish")
178+
def test_get_or_create_calls_amplitude_user_created_when_owner_created(
179+
self, amplitude_publish_mock
180+
):
181+
self.mixin_instance._get_or_create_owner(
182+
{
183+
"user": {"id": 12345, "key": "4567", "login": "testuser"},
184+
"has_private_access": False,
185+
},
186+
self.request,
187+
)
188+
189+
owner = Owner.objects.get(service_id=12345, username="testuser")
190+
191+
amplitude_publish_mock.assert_has_calls(
192+
[
193+
call("User Created", {"user_ownerid": owner.ownerid}),
194+
call("set_orgs", {"user_ownerid": owner.ownerid, "org_ids": []}),
195+
]
196+
)
197+
177198
@patch("services.analytics.AnalyticsService.user_signed_in")
178199
def test_get_or_create_calls_analytics_user_signed_in_when_owner_not_created(
179200
self, user_signed_in_mock
@@ -192,6 +213,30 @@ def test_get_or_create_calls_analytics_user_signed_in_when_owner_not_created(
192213
)
193214
user_signed_in_mock.assert_called_once()
194215

216+
@patch("shared.events.amplitude.AmplitudeEventPublisher.publish")
217+
def test_get_or_create_calls_amplitude_user_logged_in_when_owner_not_created(
218+
self, amplitude_publish_mock
219+
):
220+
owner = OwnerFactory(service_id=89, service="github", organizations=[1, 2])
221+
self.mixin_instance._get_or_create_owner(
222+
{
223+
"user": {
224+
"id": owner.service_id,
225+
"key": "02or0sa",
226+
"login": owner.username,
227+
},
228+
"has_private_access": owner.private_access,
229+
},
230+
self.request,
231+
)
232+
233+
amplitude_publish_mock.assert_has_calls(
234+
[
235+
call("User Logged in", {"user_ownerid": owner.ownerid}),
236+
call("set_orgs", {"user_ownerid": owner.ownerid, "org_ids": [1, 2]}),
237+
]
238+
)
239+
195240
@override_settings(IS_ENTERPRISE=False)
196241
@patch("services.analytics.AnalyticsService.user_signed_in")
197242
def test_set_marketing_tags_on_cookies(self, user_signed_in_mock):

codecov_auth/views/base.py

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.utils import timezone
1616
from django.utils.timezone import now
1717
from shared.encryption.token import encode_token
18+
from shared.events.amplitude import AmplitudeEventPublisher
1819
from shared.license import LICENSE_ERRORS_MESSAGES, get_current_license
1920

2021
from codecov_auth.models import Owner, OwnerProfile, Session, User
@@ -393,10 +394,21 @@ def _get_or_create_owner(
393394
owner.save(update_fields=fields_to_update)
394395

395396
marketing_tags = self.retrieve_marketing_tags_from_cookie()
397+
amplitude = AmplitudeEventPublisher()
396398
if was_created:
397399
self.analytics_service.user_signed_up(owner, **marketing_tags)
400+
amplitude.publish("User Created", {"user_ownerid": owner.ownerid})
398401
else:
399402
self.analytics_service.user_signed_in(owner, **marketing_tags)
403+
amplitude.publish("User Logged in", {"user_ownerid": owner.ownerid})
404+
orgs = owner.organizations
405+
amplitude.publish(
406+
"set_orgs",
407+
{
408+
"user_ownerid": owner.ownerid,
409+
"org_ids": orgs if orgs is not None else [],
410+
},
411+
)
400412

401413
return (owner, was_created)
402414

webhook_handlers/tests/test_github.py

+78
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,84 @@ def test_installation_creates_new_owner_if_dne_default_app(self, mock_refresh):
583583
repos_affected=[("12321", "R_kgDOG2tZYQ"), ("12343", "R_kgDOG2tABC")],
584584
)
585585

586+
@patch("shared.events.amplitude.AmplitudeEventPublisher.publish")
587+
@patch("services.task.TaskService.refresh")
588+
def test_installation_publishes_amplitude_event_without_installer(
589+
self, mock_refresh, mock_amplitude_publish
590+
):
591+
username, service_id = "newuser", 123456
592+
593+
self._post_event_data(
594+
event=GitHubWebhookEvents.INSTALLATION,
595+
data={
596+
"installation": {
597+
"id": 4,
598+
"repository_selection": "selected",
599+
"account": {"id": service_id, "login": username},
600+
"app_id": DEFAULT_APP_ID,
601+
},
602+
"repositories": [
603+
{"id": "12321", "node_id": "R_kgDOG2tZYQ"},
604+
{"id": "12343", "node_id": "R_kgDOG2tABC"},
605+
],
606+
"sender": {"type": "User"},
607+
},
608+
)
609+
610+
owner_set = Owner.objects.filter(
611+
service="github", service_id=service_id, username=username
612+
)
613+
assert owner_set.exists()
614+
owner = owner_set.first()
615+
616+
mock_amplitude_publish.assert_called_with(
617+
"App Installed",
618+
{
619+
"user_ownerid": owner.ownerid,
620+
"ownerid": owner.ownerid,
621+
},
622+
)
623+
624+
@patch("shared.events.amplitude.AmplitudeEventPublisher.publish")
625+
@patch("services.task.TaskService.refresh")
626+
def test_installation_publishes_amplitude_event_with_installer(
627+
self, mock_refresh, mock_amplitude_publish
628+
):
629+
installer = OwnerFactory(service="github", username="installer_username")
630+
631+
username, service_id = "newuser", 123456
632+
633+
self._post_event_data(
634+
event=GitHubWebhookEvents.INSTALLATION,
635+
data={
636+
"installation": {
637+
"id": 4,
638+
"repository_selection": "selected",
639+
"account": {"id": service_id, "login": username},
640+
"app_id": DEFAULT_APP_ID,
641+
},
642+
"repositories": [
643+
{"id": "12321", "node_id": "R_kgDOG2tZYQ"},
644+
{"id": "12343", "node_id": "R_kgDOG2tABC"},
645+
],
646+
"sender": {"type": "User", "login": "installer_username"},
647+
},
648+
)
649+
650+
owner_set = Owner.objects.filter(
651+
service="github", service_id=service_id, username=username
652+
)
653+
assert owner_set.exists()
654+
owner = owner_set.first()
655+
656+
mock_amplitude_publish.assert_called_with(
657+
"App Installed",
658+
{
659+
"user_ownerid": installer.ownerid,
660+
"ownerid": owner.ownerid,
661+
},
662+
)
663+
586664
@patch(
587665
"services.task.TaskService.refresh",
588666
lambda self,

webhook_handlers/views/github.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from rest_framework.permissions import AllowAny
1414
from rest_framework.response import Response
1515
from rest_framework.views import APIView
16+
from shared.events.amplitude import AmplitudeEventPublisher
1617

1718
from codecov_auth.models import (
1819
GITHUB_APP_INSTALLATION_DEFAULT_NAME,
@@ -498,9 +499,34 @@ def _handle_installation_events(
498499
# GithubWebhookEvents.INSTALLTION_REPOSITORIES also execute this code
499500
# because of deprecated flow. But the GithubAppInstallation shouldn't be changed
500501
if event == GitHubWebhookEvents.INSTALLATION:
501-
ghapp_installation, _ = GithubAppInstallation.objects.get_or_create(
502-
installation_id=installation_id, owner=owner
502+
ghapp_installation, was_created = (
503+
GithubAppInstallation.objects.get_or_create(
504+
installation_id=installation_id, owner=owner
505+
)
503506
)
507+
if was_created:
508+
installer_username = request.data.get("sender", {}).get(
509+
"login", None
510+
)
511+
installer = (
512+
Owner.objects.filter(
513+
service=self.service_name,
514+
username=installer_username,
515+
).first()
516+
if installer_username
517+
else None
518+
)
519+
# If installer does not exist, just attribute the action to the org owner.
520+
AmplitudeEventPublisher().publish(
521+
"App Installed",
522+
{
523+
"user_ownerid": installer.ownerid
524+
if installer is not None
525+
else owner.ownerid,
526+
"ownerid": owner.ownerid,
527+
},
528+
)
529+
504530
app_id = request.data["installation"]["app_id"]
505531
# Either update or set
506532
# But this value shouldn't change for the installation, so doesn't matter

0 commit comments

Comments
 (0)