Skip to content

Commit

Permalink
Feat: Implement Postmark Community Nudge Report (#1196)
Browse files Browse the repository at this point in the history
* Feat: Add email tagging functionality to mass email sending

* Chore: Remove unused postmark_nudge_report.py file

* Refactor: Update email tagging in nudges and add new report functionality

- Modified `generate_email_tag` function to include a development mode tag when not in production.
- Updated email tagging in `cadmin_events_nudge.py`, `cadmin_testimonial_nudge.py`, and `user_event_nudge.py` to use constants from `type_constants.py`.
- Introduced `postmark_nudge_report.py` to generate and send CSV reports for nudge statistics using Postmark API.

* Feat: Add shell command to Makefile for local Django environment

- Introduced a new `sh` target in the Makefile to launch the Django shell in a local environment using `DJANGO_ENV=local`.

* Feat: Implement Postmark Nudge Report functionality

- Added a new report type, POSTMARK_NUDGE_REPORT, to the constants.
- Enhanced the email sending functionality to include a new nudge report for community admins.
- Introduced methods to generate and send CSV reports based on nudge statistics using the Postmark API.
- Updated the DownloadHandler to handle requests for the new nudge report.
- Refactored existing code to integrate the new report generation and sending logic.

This commit improves the reporting capabilities for community engagement metrics.

* Refactor: Remove print statements and enhance error logging in postmark_nudge_report.py

- Eliminated print statements from various functions to streamline error handling.
- Improved error logging by ensuring all exceptions are logged consistently.
- Maintained functionality for generating and sending community reports via Postmark API.

* Fix: Update Postmark Nudge Report URL to include tag parameter

- Modified the URL in the get_stats_from_postmark function to include the 'tag' parameter for more accurate reporting.
- This change enhances the functionality of the Postmark Nudge Report by allowing filtering based on specific tags.

* Test: Add unit tests for Postmark Nudge Report functionality

- Introduced a new test suite for the Postmark Nudge Report, covering key functions such as get_stats_from_postmark, get_community_admins, generate_csv_file, send_community_report, and generate_postmark_nudge_report.
- Ensured that the tests validate the expected behavior of these functions, contributing to improved code reliability and maintainability.

* Test: Add comprehensive unit tests for Postmark Nudge Report functionality

- Introduced a new test suite in `test_postmark_nudge_report.py` to validate key functions related to Postmark Nudge Reports, including `get_stats_from_postmark`, `get_community_admins`, `generate_csv_file`, `generate_community_report_data`, `send_community_report`, and `send_user_requested_postmark_nudge_report`.
- Implemented tests to ensure correct behavior and output for generating reports and handling community data, contributing to enhanced reliability and maintainability of the codebase.

* Test: Enhance integration tests for Postmark Nudge Report functionality

- Added a new test case in `test_download.py` to validate the behavior of the Postmark Nudge Report endpoint, ensuring correct responses based on user authentication status.
- Removed outdated tests from `test_postmark_nudge_report.py` to streamline the test suite and focus on relevant functionality.
- These changes improve the reliability of the integration tests and ensure accurate reporting for community engagement metrics.

* Test: Update Postmark Nudge Report integration test

- Commented out the patch for the download_data.delay method in the test_postmark_nudge_report function within test_download.py.
- This change simplifies the test setup while maintaining the focus on validating the Postmark Nudge Report endpoint functionality.

* Test: Re-enable patch for download_data.delay in Postmark Nudge Report integration test

- Restored the patch decorator for the download_data.delay method in the test_postmark_nudge_report function within test_download.py.
- This change enhances the test setup by allowing the simulation of the download task, ensuring a more comprehensive validation of the Postmark Nudge Report endpoint functionality.
  • Loading branch information
abdullai-t authored Dec 16, 2024
1 parent b60c93d commit 7093f1a
Show file tree
Hide file tree
Showing 16 changed files with 360 additions and 15 deletions.
2 changes: 2 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,5 @@ update-recurring-schedule-prod:
DJANGO_ENV=prod python manage.py create-initial-recurring-schedule


sh:
DJANGO_ENV=local python manage.py shell
7 changes: 3 additions & 4 deletions src/_main_/utils/emailer/send_email.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import logging
from django.core.mail import send_mail, EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
import requests
from _main_.utils.massenergize_logger import log
import pystmark

from _main_.settings import IS_CANARY, POSTMARK_ACCOUNT_TOKEN, POSTMARK_EMAIL_SERVER_TOKEN, POSTMARK_DOWNLOAD_SERVER_TOKEN, IS_PROD
from _main_.settings import POSTMARK_ACCOUNT_TOKEN, POSTMARK_EMAIL_SERVER_TOKEN, POSTMARK_DOWNLOAD_SERVER_TOKEN, IS_PROD
from _main_.utils.constants import ME_INBOUND_EMAIL_ADDRESS
from _main_.utils.utils import is_test_mode, run_in_background

Expand Down Expand Up @@ -37,13 +36,13 @@ def send_massenergize_email(subject, msg, to, sender=None):
return False
return True

def send_massenergize_email_with_attachments(temp, t_model, to, file, file_name, sender=None):
def send_massenergize_email_with_attachments(temp, t_model, to, file, file_name, sender=None, tag=None):
if is_test_mode():
return True
t_model = {**t_model, "is_dev":is_dev_env()}


message = pystmark.Message(sender=sender or FROM_EMAIL, to=to, template_alias=temp, template_model=t_model)
message = pystmark.Message(sender=sender or FROM_EMAIL, to=to, template_alias=temp, template_model=t_model, tag=tag)
# postmark server can be Production, Development or Testing (for local testing)
postmark_server = POSTMARK_EMAIL_SERVER_TOKEN
if file is not None:
Expand Down
1 change: 1 addition & 0 deletions src/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
CAMPAIGN_VIEWS_PERFORMANCE_REPORT = 'campaign_views_performance_report'
CAMPAIGN_INTERACTION_PERFORMANCE_REPORT = 'campaign_interaction_performance_report'
CAMPAIGN_INTERACTION_PERFORMANCE_REPORT = 'campaign_interaction_performance_report'
POSTMARK_NUDGE_REPORT = "postmark_nudge_report"

STANDARD_USER = 'standard_user'
GUEST_USER = 'guest_user'
Expand Down
12 changes: 12 additions & 0 deletions src/api/handlers/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def registerRoutes(self) -> None:
self.add("/downloads.sample.user_report", self.send_sample_user_report)
self.add("/downloads.action.users", self.action_users_download)
self.add("/downloads.pagemap", self.community_pagemap_download)
self.add("/downloads.postmark.nudge_report", self.download_postmark_nudge_report)


self.add("/downloads.campaigns.follows", self.campaign_follows_download)
Expand Down Expand Up @@ -232,3 +233,14 @@ def campaign_interaction_performance_download(self, request):



@admins_only
def download_postmark_nudge_report(self, request):
context: Context = request.context
args: dict = context.args
community_id = args.pop('community_id', None)
period = args.get("period")
report, err = self.service.download_postmark_nudge_report(context, community_id=community_id, period=period)

if err:
return MassenergizeResponse(error=str(err), status=err.status)
return MassenergizeResponse(data={}, status=200)
15 changes: 14 additions & 1 deletion src/api/services/download.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from _main_.utils.massenergize_errors import MassEnergizeAPIError
from api.constants import ACTION_USERS, ACTIONS, CAMPAIGN_INTERACTION_PERFORMANCE_REPORT, CAMPAIGN_PERFORMANCE_REPORT, CAMPAIGN_VIEWS_PERFORMANCE_REPORT, COMMUNITIES, FOLLOWED_REPORT, LIKE_REPORT, LINK_PERFORMANCE_REPORT, METRICS, SAMPLE_USER_REPORT, TEAMS, USERS, CADMIN_REPORT, SADMIN_REPORT, COMMUNITY_PAGEMAP
from api.constants import ACTION_USERS, ACTIONS, CAMPAIGN_INTERACTION_PERFORMANCE_REPORT, CAMPAIGN_PERFORMANCE_REPORT, CAMPAIGN_VIEWS_PERFORMANCE_REPORT, COMMUNITIES, FOLLOWED_REPORT, LIKE_REPORT, LINK_PERFORMANCE_REPORT, METRICS, POSTMARK_NUDGE_REPORT, SAMPLE_USER_REPORT, TEAMS, USERS, CADMIN_REPORT, SADMIN_REPORT, COMMUNITY_PAGEMAP
from api.store.download import DownloadStore
from _main_.utils.context import Context
from typing import Tuple
Expand Down Expand Up @@ -82,6 +82,19 @@ def send_cadmin_report(self, context: Context, community_id=None) -> Tuple[list,
download_data.delay(data, CADMIN_REPORT)
return [], None

def download_postmark_nudge_report(self, context: Context, community_id=None, period=45) -> Tuple[list, MassEnergizeAPIError]:
data = {
'community_id': community_id,
'user_is_community_admin': context.user_is_community_admin,
'user_is_super_admin':context.user_is_super_admin,
'email': context.user_email,
'user_is_logged_in': context.user_is_logged_in,
"period":period

}
download_data.delay(data, POSTMARK_NUDGE_REPORT)
return [], None

def send_sample_user_report(self, context: Context, community_id) -> Tuple[dict, MassEnergizeAPIError]:
data = {'email': context.user_email, "community_id": community_id}
download_data.delay(data, SAMPLE_USER_REPORT)
Expand Down
6 changes: 5 additions & 1 deletion src/api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from _main_.utils.massenergize_logger import log
from api.constants import ACTIONS, CADMIN_REPORT, CAMPAIGN_INTERACTION_PERFORMANCE_REPORT, CAMPAIGN_PERFORMANCE_REPORT, \
CAMPAIGN_VIEWS_PERFORMANCE_REPORT, COMMUNITIES, COMMUNITY_PAGEMAP, DOWNLOAD_POLICY, FOLLOWED_REPORT, LIKE_REPORT, \
LINK_PERFORMANCE_REPORT, METRICS, SAMPLE_USER_REPORT, TEAMS, USERS
LINK_PERFORMANCE_REPORT, METRICS, POSTMARK_NUDGE_REPORT, SAMPLE_USER_REPORT, TEAMS, USERS
from api.services.translations_cache import TranslationsCacheService
from api.store.common import create_pdf_from_rich_text, sign_mou
from api.store.download import DownloadStore
Expand All @@ -26,6 +26,7 @@
Policy, \
UserActionRel, UserProfile
from task_queue.nudges.cadmin_events_nudge import generate_event_list_for_community, send_events_report
from task_queue.nudges.postmark_nudge_report import send_user_requested_postmark_nudge_report
from task_queue.nudges.user_event_nudge import prepare_user_events_nudge
from django.core.cache import cache
from _main_.celery.app import app
Expand Down Expand Up @@ -127,6 +128,9 @@ def download_data(self, args, download_type):
elif download_type == SAMPLE_USER_REPORT:
prepare_user_events_nudge(email=email, community_id=args.get("community_id"))

elif download_type == POSTMARK_NUDGE_REPORT:
send_user_requested_postmark_nudge_report(community_id=args.get("community_id"), email=email, period=args.get("period", 45))

elif download_type == DOWNLOAD_POLICY:
policy = Policy.objects.filter(id=args.get("policy_id")).first()
rich_text = sign_mou(policy.description)
Expand Down
18 changes: 18 additions & 0 deletions src/api/tests/integration/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.conf import settings as django_settings
from urllib.parse import urlencode
from _main_.settings import BASE_DIR
from _main_.utils.massenergize_errors import MassEnergizeAPIError
from _main_.utils.massenergize_response import MassenergizeResponse
from database.models import Team, Community, UserProfile, TeamMember, CommunityAdminGroup, CommunityMember, Action
from api.tests.common import signinAs, createUsers
Expand Down Expand Up @@ -184,6 +185,23 @@ def test_download_actions(self, mocked_delay):
self.assertTrue(isinstance(response, MassenergizeResponse))
self.assertTrue(response.toDict().get("success"))

@patch("api.tasks.download_data.delay", return_value=None)
def test_postmark_nudge_report(self, mock_delay):
endpoint = "/api/downloads.postmark.nudge_report"

signinAs(self.client, self.CADMIN)
response = self.client.post(endpoint, urlencode({"community_id": self.COMMUNITY.id}), content_type="application/x-www-form-urlencoded")
self.assertEquals(type(response), MassenergizeResponse)
self.assertTrue(response.toDict().get("success"))

signinAs(self.client, None)
response = self.client.post(endpoint, urlencode({"community_id": self.COMMUNITY.id}), content_type="application/x-www-form-urlencoded")
self.assertFalse(response.toDict().get("success"))

signinAs(self.client, self.USER)
response = self.client.post(endpoint, urlencode({"community_id": self.COMMUNITY.id}), content_type="application/x-www-form-urlencoded")
self.assertFalse(response.toDict().get("success"))


def test_download_communities(self):
pass
Expand Down
41 changes: 41 additions & 0 deletions src/api/tests/unit/tasks/test_postmark_nudge_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from django.test import TestCase
from task_queue.nudges.postmark_nudge_report import (
get_stats_from_postmark,
get_community_admins,
generate_csv_file,
send_community_report,
generate_postmark_nudge_report
)
from database.models import Community, UserProfile, CommunityAdminGroup

class PostmarkNudgeReportTests(TestCase):

def setUp(self):
self.community = Community.objects.create(name="Test Community", subdomain="test")
self.user = UserProfile.objects.create(email="[email protected]", full_name="Test User")
ad = CommunityAdminGroup.objects.create(community=self.community)
ad.members.add(self.user)

def test_get_stats_from_postmark(self):
response = get_stats_from_postmark("test_tag", "2023-01-01", "2023-01-31")
self.assertIsNotNone(response)

def test_get_community_admins(self):
# Test getting community admins
admins = get_community_admins(self.community)
self.assertIn(self.user.email, admins)

def test_generate_csv_file(self):
rows = [["Header1", "Header2"], ["Row1Col1", "Row1Col2"]]
csv_content = generate_csv_file(rows)
self.assertIsNotNone(csv_content)

def test_send_community_report(self):
report = b"Test report content"
filename = "test_report.csv"
send_community_report(report, self.community, filename, self.user)


def test_generate_postmark_nudge_report(self):
result = generate_postmark_nudge_report()
self.assertTrue(result)
7 changes: 6 additions & 1 deletion src/api/utils/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from math import atan2, cos, radians, sin, sqrt

from django.utils.text import slugify
from _main_.settings import IS_PROD
from _main_.utils.constants import COMMUNITY_URL_ROOT, DEFAULT_SOURCE_LANGUAGE_CODE
from _main_.utils.utils import load_json
from apps__campaigns.models import CallToAction, Section
Expand Down Expand Up @@ -401,4 +402,8 @@ def create_unique_slug(title, model, field_name="slug", user_defined_slug=None):
return f"{slug}-{timestamp}".lower()



def generate_email_tag(subdomain, nudge_name):
tag = f"{subdomain}||{nudge_name}"
if not IS_PROD:
return tag+ "##DEV_MODE"
return tag
2 changes: 2 additions & 0 deletions src/task_queue/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from task_queue.database_tasks.update_actions_content import update_actions_content
from task_queue.nudges.cadmin_events_nudge import send_events_nudge
from task_queue.nudges.cadmin_testimonial_nudge import prepare_testimonials_for_community_admins
from task_queue.nudges.postmark_nudge_report import generate_postmark_nudge_report
from task_queue.nudges.user_event_nudge import prepare_user_events_nudge
from task_queue.nudges.postmark_sender_signature import collect_and_create_signatures
from task_queue.database_tasks.media_library_cleanup import remove_duplicate_images
Expand All @@ -30,4 +31,5 @@
"Remove Duplicate Images": remove_duplicate_images,
"Translate Database Contents": TranslateDBContents().start_translations,
"Community Admin Testimonial Nudge": prepare_testimonials_for_community_admins,
"Postmark Nudge Report": generate_postmark_nudge_report
}
11 changes: 7 additions & 4 deletions src/task_queue/nudges/cadmin_events_nudge.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
from _main_.utils.constants import ADMIN_URL_ROOT, COMMUNITY_URL_ROOT
from _main_.utils.emailer.send_email import send_massenergize_email_with_attachments
from _main_.utils.feature_flag_keys import COMMUNITY_ADMIN_WEEKLY_EVENTS_NUDGE_FF
from api.utils.api_utils import generate_email_tag
from api.utils.constants import WEEKLY_EVENTS_NUDGE_TEMPLATE
from database.models import Community, CommunityAdminGroup, Event, FeatureFlag, UserProfile
from database.utils.settings.model_constants.events import EventConstants
from task_queue.helpers import get_event_location
from task_queue.nudges.nudge_utils import get_admin_email_list, update_last_notification_dates
from _main_.utils.massenergize_logger import log
from task_queue.type_constants import CADMIN_EVENTS_NUDGE



Expand Down Expand Up @@ -142,7 +144,7 @@ def send_events_nudge(task=None) -> bool:
for email, data in email_list.items():
name = data.get("name")

stat = send_events_report(name, email, event_list)
stat = send_events_report(name, email, event_list, com)

if not stat:
log.error("send_events_report error return")
Expand All @@ -157,7 +159,7 @@ def send_events_nudge(task=None) -> bool:
return False


def send_events_report(name, email, event_list) -> bool:
def send_events_report(name, email, event_list, com) -> bool:
try:
# 14-Dec-23 - fix for user_info not provided
user = UserProfile.objects.filter(email=email).first()
Expand All @@ -169,8 +171,9 @@ def send_events_report(name, email, event_list) -> bool:
data["change_preference_link"] = change_preference_link
data["events"] = event_list

# sent from MassEnergize to cadmins
send_massenergize_email_with_attachments(WEEKLY_EVENTS_NUDGE_TEMPLATE, data, [email], None, None, None)
tag = generate_email_tag(com.subdomain, CADMIN_EVENTS_NUDGE)

send_massenergize_email_with_attachments(WEEKLY_EVENTS_NUDGE_TEMPLATE, data, [email], None, None, None, tag)
return True
except Exception as e:
log.error("send_events_report exception: " + str(e))
Expand Down
8 changes: 6 additions & 2 deletions src/task_queue/nudges/cadmin_testimonial_nudge.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from _main_.utils.emailer.send_email import send_massenergize_email_with_attachments
from _main_.utils.feature_flag_keys import TESTIMONIAL_AUTO_SHARE_SETTINGS_NUDGE_FEATURE_FLAG_KEY
from _main_.utils.massenergize_logger import log
from api.utils.api_utils import get_sender_email
from api.utils.api_utils import generate_email_tag, get_sender_email
from api.utils.constants import CADMIN_TESTIMONIAL_NUDGE_TEMPLATE
from database.models import Community, CommunityAdminGroup, FeatureFlag, Testimonial
from task_queue.helpers import get_summary
Expand All @@ -13,6 +13,8 @@
from django.db.models import Q
from django.utils import timezone

from task_queue.type_constants import CADMIN_TESTIMONIALS_NUDGE


TESTIMONIAL_NUDGE_KEY = "cadmin_testimonial_nudge"

Expand Down Expand Up @@ -79,8 +81,10 @@ def send_nudge(data, community, admin):
login_method = user_info.get("login_method") if user_info else None
cred = encode_data_for_URL({"email": email, "login_method": login_method})
data["change_preference_link"] = f"{ADMIN_URL_ROOT}/admin/profile/preferences/?cred={cred}"

tag = generate_email_tag(community.subdomain, CADMIN_TESTIMONIALS_NUDGE)

send_massenergize_email_with_attachments(CADMIN_TESTIMONIAL_NUDGE_TEMPLATE, data, [email], None, None, get_sender_email(community.id))
send_massenergize_email_with_attachments(CADMIN_TESTIMONIAL_NUDGE_TEMPLATE, data, [email], None, None, get_sender_email(community.id), tag)


return True
Expand Down
Loading

0 comments on commit 7093f1a

Please sign in to comment.