Skip to content

Commit

Permalink
Remove weekly email code (#8643)
Browse files Browse the repository at this point in the history
This is so out of date to be useless right now and we're purging all pg
code
  • Loading branch information
macobo authored Feb 16, 2022
1 parent bdce803 commit 294e36d
Show file tree
Hide file tree
Showing 4 changed files with 3 additions and 237 deletions.
11 changes: 0 additions & 11 deletions posthog/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ def setup_periodic_tasks(sender: Celery, **kwargs):
if getattr(settings, "MULTI_TENANCY", False):
sender.add_periodic_task(crontab(hour=0, minute=0), calculate_billing_daily_usage.s()) # every day midnight UTC

# Send weekly email report (~ 8:00 SF / 16:00 UK / 17:00 EU)
sender.add_periodic_task(crontab(day_of_week="mon", hour=15, minute=0), send_weekly_email_report.s())

sender.add_periodic_task(crontab(day_of_week="fri", hour=0, minute=0), clean_stale_partials.s())

# delete old plugin logs every 4 hours
Expand Down Expand Up @@ -332,14 +329,6 @@ def calculate_cohort_ids_in_feature_flags_task():
calculate_cohort_ids_in_feature_flags()


@app.task(ignore_result=True)
def send_weekly_email_report():
if settings.EMAIL_REPORTS_ENABLED:
from posthog.tasks.email import send_weekly_email_reports

send_weekly_email_reports()


@app.task(ignore_result=True, bind=True)
def debug_task(self):
print(f"Request: {self.request!r}")
Expand Down
3 changes: 0 additions & 3 deletions posthog/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,3 @@

# Lastly, cloud settings override and modify all
from posthog.settings.cloud import *

# TODO: Temporary
EMAIL_REPORTS_ENABLED: bool = get_from_env("EMAIL_REPORTS_ENABLED", False, type_cast=str_to_bool)
108 changes: 2 additions & 106 deletions posthog/tasks/email.py
Original file line number Diff line number Diff line change
@@ -1,126 +1,22 @@
import datetime
import logging
import uuid
from typing import Optional

import structlog
from django.conf import settings

from posthog.celery import app
from posthog.email import EmailMessage, is_email_available
from posthog.models import Event, Organization, OrganizationInvite, PersonDistinctId, Team, User
from posthog.templatetags.posthog_filters import compact_number
from posthog.utils import get_previous_week
from posthog.email import EmailMessage
from posthog.models import Organization, OrganizationInvite, User

logger = structlog.get_logger(__name__)


def send_weekly_email_reports() -> None:
"""
Schedules an async task to send the weekly email report for each team.
"""

if not is_email_available():
logger.info("Skipping send_weekly_email_report because email is not properly configured")
return

for team in Team.objects.order_by("pk"):
_send_weekly_email_report_for_team.delay(team_id=team.pk)


def send_message_to_all_staff_users(message: EmailMessage) -> None:
for user in User.objects.filter(is_staff=True):
message.add_recipient(email=user.email, name=user.first_name)

message.send()


@app.task(ignore_result=True, max_retries=1)
def _send_weekly_email_report_for_team(team_id: int) -> None:
"""
Sends the weekly email report to all users in a team.
"""

period_start, period_end = get_previous_week()
last_week_start: datetime.datetime = period_start - datetime.timedelta(7)
last_week_end: datetime.datetime = period_end - datetime.timedelta(7)

campaign_key: str = f"weekly_report_for_team_{team_id}_on_{period_start.strftime('%Y-%m-%d')}"

team = Team.objects.get(pk=team_id)

event_data_set = Event.objects.filter(team=team, timestamp__gte=period_start, timestamp__lte=period_end,)

active_users = PersonDistinctId.objects.filter(
distinct_id__in=event_data_set.values("distinct_id").distinct(),
).distinct()
active_users_count: int = active_users.count()

if active_users_count == 0:
# TODO: Send an email prompting fix to no active users
return

last_week_users = PersonDistinctId.objects.filter(
distinct_id__in=Event.objects.filter(team=team, timestamp__gte=last_week_start, timestamp__lte=last_week_end,)
.values("distinct_id")
.distinct(),
).distinct()
last_week_users_count: int = last_week_users.count()

two_weeks_ago_users = PersonDistinctId.objects.filter(
distinct_id__in=Event.objects.filter(
team=team,
timestamp__gte=last_week_start - datetime.timedelta(7),
timestamp__lte=last_week_end - datetime.timedelta(7),
)
.values("distinct_id")
.distinct(),
).distinct() # used to compute delta in churned users
two_weeks_ago_users_count: int = two_weeks_ago_users.count()

not_last_week_users = PersonDistinctId.objects.filter(
pk__in=active_users.difference(last_week_users,).values_list("pk", flat=True,)
) # users that were present this week but not last week

churned_count = last_week_users.difference(active_users).count()
churned_ratio: Optional[float] = (churned_count / last_week_users_count if last_week_users_count > 0 else None)
last_week_churn_ratio: Optional[float] = (
two_weeks_ago_users.difference(last_week_users).count() / two_weeks_ago_users_count
if two_weeks_ago_users_count > 0
else None
)
churned_delta: Optional[float] = (
churned_ratio / last_week_churn_ratio - 1 if last_week_churn_ratio else None # type: ignore
)

message = EmailMessage(
campaign_key=campaign_key,
subject=f"PostHog weekly report for {period_start.strftime('%b %d, %Y')} to {period_end.strftime('%b %d')}",
template_name="weekly_report",
template_context={
"preheader": f"Your PostHog weekly report is ready! Your team had {compact_number(active_users_count)} active users last week! 🎉",
"team": team.name,
"period_start": period_start,
"period_end": period_end,
"active_users": active_users_count,
"active_users_delta": active_users_count / last_week_users_count - 1 if last_week_users_count > 0 else None,
"user_distribution": {
"new": not_last_week_users.filter(person__created_at__gte=period_start).count() / active_users_count,
"retained": active_users.intersection(last_week_users).count() / active_users_count,
"resurrected": not_last_week_users.filter(person__created_at__lt=period_start).count()
/ active_users_count,
},
"churned_users": {"abs": churned_count, "ratio": churned_ratio, "delta": churned_delta},
},
)

for user in team.organization.members.all():
# TODO: Skip "unsubscribed" users
message.add_recipient(email=user.email, name=user.first_name)

message.send()


@app.task(max_retries=1)
def send_invite(invite_id: str) -> None:
campaign_key: str = f"invite_email_{invite_id}"
Expand Down
118 changes: 1 addition & 117 deletions posthog/test/test_email.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import datetime
import hashlib
from typing import List
from unittest.mock import patch

import pytz
from constance.test import override_config
from django.conf import settings
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.utils import timezone
from freezegun import freeze_time

from posthog.email import EmailMessage, _send_email
from posthog.models import Event, MessagingRecord, Organization, Person, Team, User
from posthog.tasks.email import send_weekly_email_reports
from posthog.models import MessagingRecord, Organization, Person, Team, User
from posthog.test.base import BaseTest


Expand Down Expand Up @@ -41,34 +34,6 @@ def setUp(self):
defaults={"sent_at": timezone.now()},
) # This user should not get the emails

last_week = datetime.datetime(2020, 9, 17, 3, 22, tzinfo=pytz.UTC)
two_weeks_ago = datetime.datetime(2020, 9, 8, 19, 54, tzinfo=pytz.UTC)

self.persons: List = [self.create_person(self.team, str(i)) for i in range(0, 7)]

# Resurrected
self.persons[0].created_at = timezone.now() - datetime.timedelta(weeks=3)
self.persons[0].save()
self.persons[1].created_at = timezone.now() - datetime.timedelta(weeks=4)
self.persons[1].save()
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=0)
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=1)

# Retained
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=2)
Event.objects.create(team=self.team, timestamp=two_weeks_ago, distinct_id=2)
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=3)
Event.objects.create(team=self.team, timestamp=two_weeks_ago, distinct_id=3)
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=4)
Event.objects.create(team=self.team, timestamp=two_weeks_ago, distinct_id=4)

# New
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=5)
Event.objects.create(team=self.team, timestamp=last_week, distinct_id=5)

# Churned
Event.objects.create(team=self.team, timestamp=two_weeks_ago, distinct_id=6)

def test_cant_send_emails_if_not_properly_configured(self) -> None:
with override_config(EMAIL_HOST=None):
with self.assertRaises(ImproperlyConfigured) as e:
Expand Down Expand Up @@ -105,84 +70,3 @@ def test_cant_send_same_campaign_twice(self) -> None:

record.refresh_from_db()
self.assertEqual(record.sent_at, sent_at)

@freeze_time("2020-09-21")
@override_config(EMAIL_HOST="localhost")
def test_weekly_email_report(self) -> None:

record_count: int = MessagingRecord.objects.count()

expected_recipients: List[str] = ["test@posthog.com", "test2@posthog.com"]

with self.settings(CELERY_TASK_ALWAYS_EAGER=True, SITE_URL="http://localhost:9999"):
send_weekly_email_reports()

self.assertSetEqual({",".join(outmail.to) for outmail in mail.outbox}, set(expected_recipients))

self.assertEqual(
mail.outbox[0].subject, "PostHog weekly report for Sep 14, 2020 to Sep 20",
)

self.assertEqual(
mail.outbox[0].body, "",
) # no plain-text version support yet

html_message = mail.outbox[0].alternatives[0][0] # type: ignore
self.validate_basic_html(
html_message,
"http://localhost:9999",
preheader="Your PostHog weekly report is ready! Your team had 6 active users last week! 🎉",
)

# Ensure records are properly saved to prevent duplicate emails
self.assertEqual(MessagingRecord.objects.count(), record_count + 2)
for email in expected_recipients:
email_hash = hashlib.sha256(f"{settings.SECRET_KEY}_{email}".encode()).hexdigest()
record = MessagingRecord.objects.get(
email_hash=email_hash, campaign_key=f"weekly_report_for_team_{self.team.pk}_on_2020-09-14",
)
self.assertTrue((timezone.now() - record.sent_at).total_seconds() < 5)

@patch("posthog.tasks.email.EmailMessage")
@override_config(EMAIL_HOST="localhost")
@freeze_time("2020-09-21")
def test_weekly_email_report_content(self, mock_email_message):

with self.settings(CELERY_TASK_ALWAYS_EAGER=True):
send_weekly_email_reports()

self.assertEqual(
mock_email_message.call_args[1]["campaign_key"], f"weekly_report_for_team_{self.team.pk}_on_2020-09-14",
) # Campaign key
self.assertEqual(
mock_email_message.call_args[1]["subject"], "PostHog weekly report for Sep 14, 2020 to Sep 20",
) # Email subject
self.assertEqual(mock_email_message.call_args[1]["template_name"], "weekly_report")

template_context = mock_email_message.call_args[1]["template_context"]

self.assertEqual(template_context["team"], "The Bakery")
self.assertEqual(
template_context["period_start"], datetime.datetime(2020, 9, 14, tzinfo=pytz.UTC),
)
self.assertEqual(
template_context["period_end"], datetime.datetime(2020, 9, 20, 23, 59, 59, 999999, tzinfo=pytz.UTC),
)
self.assertEqual(
template_context["active_users"], 6,
)
self.assertEqual(
template_context["active_users_delta"], 0.5,
)
self.assertEqual(
round(template_context["user_distribution"]["new"], 2), 0.17,
)
self.assertEqual(
template_context["user_distribution"]["retained"], 0.5,
)
self.assertEqual(
round(template_context["user_distribution"]["resurrected"], 2), 0.33,
)
self.assertEqual(
template_context["churned_users"], {"abs": 1, "ratio": 0.25, "delta": None},
)

0 comments on commit 294e36d

Please sign in to comment.