forked from move-coop/parsons
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EveryAction: Add email endpoint methods to retrieve email stats from …
…TargetedEmail (move-coop#1003) * added head and tail methods to parsons table * adding tests and docs to head and tail * added email endpoint with email stats transformation * Add test cases for getting email and email stats * Revert "added head and tail methods to parsons table" This reverts commit 2a4290d. * Revert "adding tests and docs to head and tail" This reverts commit a885d11. * fixed tests * feat: Add ascending/descending sorting option to get_emails method The `get_emails` method now accepts an optional `ascending` parameter to specify the sorting order for the `dateModified` field. By default, the emails are sorted in ascending order. This change allows users to retrieve emails in either ascending or descending order based on their preference. * removed print --------- Co-authored-by: matthewkrausse <[email protected]> Co-authored-by: mkrausse-ggtx <[email protected]> Co-authored-by: Shauna <[email protected]>
- Loading branch information
1 parent
e491cef
commit 66f288e
Showing
3 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
from parsons.etl.table import Table | ||
import logging | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Email(object): | ||
""" | ||
Instantiate the Email class. | ||
You can find the docs for the NGP VAN Email API here: | ||
https://docs.ngpvan.com/reference/email-overview | ||
""" | ||
|
||
def __init__(self, van_connection): | ||
|
||
self.connection = van_connection | ||
|
||
def get_emails(self, ascending: bool = True) -> Table: | ||
""" | ||
Get emails. | ||
`Args:` | ||
ascending : Bool | ||
sorts results in ascending or descending order | ||
for the dateModified field. Defaults to True (ascending). | ||
`Returns:` | ||
Parsons Table | ||
See :ref:`parsons-table` for output options. | ||
""" | ||
if ascending: | ||
params = { | ||
"$orderby": "dateModified asc", | ||
} | ||
if not ascending: | ||
params = { | ||
"$orderby": "dateModified desc", | ||
} | ||
|
||
tbl = Table(self.connection.get_request("email/messages", params=params)) | ||
logger.debug(f"Found {tbl.num_rows} emails.") | ||
return tbl | ||
|
||
def get_email(self, email_id: int, expand: bool = True) -> Table: | ||
""" | ||
Get an email. | ||
Note that it takes some time for the system to aggregate opens and click-throughs, | ||
so data can be delayed up to 15 minutes. | ||
`Args:` | ||
email_id : int | ||
The email id. | ||
expand : bool | ||
Optional; expands the email message to include the email content and | ||
statistics. Defaults to True. | ||
`Returns:` | ||
dict | ||
""" | ||
|
||
params = { | ||
"$expand": ( | ||
"emailMessageContent, EmailMessageContentDistributions" | ||
if expand | ||
else None | ||
), | ||
} | ||
|
||
r = self.connection.get_request(f"email/message/{email_id}", params=params) | ||
logger.debug(f"Found email {email_id}.") | ||
return r | ||
|
||
def get_email_stats(self) -> Table: | ||
""" | ||
Get stats for all emails, aggregating any A/B tests. | ||
`Args:` | ||
emails : list | ||
A list of email message details. | ||
`Returns:` | ||
Parsons Table | ||
See :ref:`parsons-table` for output options. | ||
""" | ||
|
||
email_list = [] | ||
|
||
final_email_list = [] | ||
|
||
emails = self.get_emails() | ||
|
||
foreign_message_ids = [email["foreignMessageId"] for email in emails] | ||
|
||
for fmid in foreign_message_ids: | ||
email = self.get_email(fmid) | ||
email_list.append(email) | ||
|
||
for email in email_list: | ||
d = {} | ||
d["name"] = email["name"] | ||
d["createdBy"] = email["createdBy"] | ||
d["dateCreated"] = email["dateCreated"] | ||
d["dateModified"] = email["dateModified"] | ||
d["dateScheduled"] = email["dateScheduled"] | ||
d["foreignMessageId"] = email["foreignMessageId"] | ||
d["recipientCount"] = 0 | ||
d["bounceCount"] = 0 | ||
d["contributionCount"] = 0 | ||
d["contributionTotal"] = 0 | ||
d["formSubmissionCount"] = 0 | ||
d["linksClickedCount"] = 0 | ||
d["machineOpenCount"] = 0 | ||
d["openCount"] = 0 | ||
d["unsubscribeCount"] = 0 | ||
try: | ||
for i in email["emailMessageContent"]: | ||
d["recipientCount"] += i["emailMessageContentDistributions"][ | ||
"recipientCount" | ||
] | ||
d["bounceCount"] += i["emailMessageContentDistributions"][ | ||
"bounceCount" | ||
] | ||
d["contributionCount"] += i["emailMessageContentDistributions"][ | ||
"contributionCount" | ||
] | ||
d["contributionTotal"] += i["emailMessageContentDistributions"][ | ||
"contributionTotal" | ||
] | ||
d["formSubmissionCount"] += i["emailMessageContentDistributions"][ | ||
"formSubmissionCount" | ||
] | ||
d["linksClickedCount"] += i["emailMessageContentDistributions"][ | ||
"linksClickedCount" | ||
] | ||
d["machineOpenCount"] += i["emailMessageContentDistributions"][ | ||
"machineOpenCount" | ||
] | ||
d["openCount"] += i["emailMessageContentDistributions"]["openCount"] | ||
d["unsubscribeCount"] += i["emailMessageContentDistributions"][ | ||
"unsubscribeCount" | ||
] | ||
except TypeError as e: | ||
logger.info(str(e)) | ||
pass | ||
|
||
final_email_list.append(d) | ||
|
||
return Table(final_email_list) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import unittest | ||
import os | ||
import requests_mock | ||
from parsons import VAN, Table | ||
|
||
|
||
def assert_matching_tables(table1, table2, ignore_headers=False): | ||
if ignore_headers: | ||
data1 = table1.data | ||
data2 = table2.data | ||
else: | ||
data1 = table1 | ||
data2 = table2 | ||
|
||
if isinstance(data1, Table) and isinstance(data2, Table): | ||
assert data1.num_rows == data2.num_rows | ||
|
||
for r1, r2 in zip(data1, data2): | ||
# Cast both rows to lists, in case they are different types of collections. Must call | ||
# .items() on dicts to compare content of collections | ||
if isinstance(r1, dict): | ||
r1 = r1.items() | ||
if isinstance(r2, dict): | ||
r2 = r2.items() | ||
|
||
assert list(r1) == list(r2) | ||
|
||
|
||
os.environ["VAN_API_KEY"] = "SOME_KEY" | ||
|
||
mock_response = [ | ||
{ | ||
"foreignMessageId": "oK2ahdAcEe6F-QAiSCI3lA2", | ||
"name": "Test Email", | ||
"createdBy": "Joe Biden", | ||
"dateCreated": "2024-02-20T13:20:00Z", | ||
"dateScheduled": "2024-02-20T15:55:00Z", | ||
"campaignID": 0, | ||
"dateModified": "2024-02-20T15:54:36.27Z", | ||
"emailMessageContent": None, | ||
}, | ||
{ | ||
"foreignMessageId": "rjzc2szzEe6F-QAiSCI3lA2", | ||
"name": "Test Email 2", | ||
"createdBy": "Joe Biden", | ||
"dateCreated": "2024-02-16T12:49:00Z", | ||
"dateScheduled": "2024-02-16T13:29:00Z", | ||
"campaignID": 0, | ||
"dateModified": "2024-02-16T13:29:16.453Z", | ||
"emailMessageContent": None, | ||
}, | ||
{ | ||
"foreignMessageId": "_E1AfcnkEe6F-QAiSCI3lA2", | ||
"name": "Test Email 3", | ||
"createdBy": "Joe Biden", | ||
"dateCreated": "2024-02-12T15:26:00Z", | ||
"dateScheduled": "2024-02-13T11:22:00Z", | ||
"campaignID": 0, | ||
"dateModified": "2024-02-13T11:22:28.273Z", | ||
"emailMessageContent": None, | ||
}, | ||
{ | ||
"foreignMessageId": "6GTLBsUwEe62YAAiSCIxlw2", | ||
"name": "Test Email 4", | ||
"createdBy": "Joe Biden", | ||
"dateCreated": "2024-02-06T15:47:00Z", | ||
"dateScheduled": "2024-02-07T10:32:00Z", | ||
"campaignID": 0, | ||
"dateModified": "2024-02-07T10:31:55.16Z", | ||
"emailMessageContent": None, | ||
}, | ||
{ | ||
"foreignMessageId": "mgTdmcEiEe62YAAiSCIxlw2", | ||
"name": "Test Email 5", | ||
"createdBy": "Joe Biden", | ||
"dateCreated": "2024-02-01T11:55:00Z", | ||
"dateScheduled": "2024-02-01T16:08:00Z", | ||
"campaignID": 0, | ||
"dateModified": "2024-02-01T16:08:10.737Z", | ||
"emailMessageContent": None, | ||
}, | ||
] | ||
|
||
|
||
class TestEmail(unittest.TestCase): | ||
def setUp(self): | ||
self.van = VAN(os.environ["VAN_API_KEY"], db="MyVoters", raise_for_status=False) | ||
|
||
@requests_mock.Mocker() | ||
def test_get_email_messages(self, m): | ||
m.get( | ||
self.van.connection.uri + "email/messages", | ||
json=mock_response, | ||
status_code=200, | ||
) | ||
|
||
response = self.van.get_emails() | ||
|
||
assert_matching_tables(response, mock_response) | ||
|
||
@requests_mock.Mocker() | ||
def test_get_email_message(self, m): | ||
m.get( | ||
self.van.connection.uri + "email/message/1", | ||
json=mock_response[0], | ||
status_code=200, | ||
) | ||
|
||
response = self.van.get_email(1) | ||
|
||
assert_matching_tables(response, mock_response[0]) |