-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mpp 2062 return trackers found removed in email header context #2232
Mpp 2062 return trackers found removed in email header context #2232
Conversation
Use level one and two instead of general and strict
for email trackers removed
for email tracker removed report on relayed email as well as cleaning old foxfooding work
if there is no trackers removed
on RelayAddresses, DomainAddresses, and for deleted addresses on Profile
and remove override_sample since it was used for Foxfooding and not needed anymore
✅ Deploy Preview for fx-relay-demo canceled.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @say-yawn! Some required changes:
- Make the tracker counts read-only fields in the API
- Update the database schema to ensure that data isn't lost during deployment
and a bunch of nits.
My testing process (after a few iterations!):
- Add waffle flag "tracker_removal", enable for all users
- Set "remove_level_one_email_trackers" for my test user. At first, I did this manually. After rebuilding the front-end I could see how to do it in my user settings.
- Send an HTML email with 2 links to a level-2 tracker and 1 to a strict tracker. The link text was the URL.
In the email processing log, I saw the event log:
{"Timestamp": 1658435438043749120, "Type": "eventsinfo", "Logger": "fx-private-relay", "Hostname": "johnwhitlock-4zq05n", "EnvVersion": "2.0", "Severity": 6, "Pid": 18832, "Fields": {"level": "general", "tracker_removed": 2, "level_one": {"count": 4, "trackers": {"8d8.biz": 4}}, "level_two": {"count": 2, "trackers": {"1-2-1marketing.com": 2}}, "msg": "email_tracker_summary"}}
I'm not sure why it detected 4 trackers and removed 2. Maybe because the link text was the URL? That remained in the forwarded email.
In the relayed message, the level one tracker links were replaced with a link to /faq
. The header had the text "2 email trackers removed - Learn more". "Learn more" was a link to the report:
The linked report had more data, including a very old "Received by" time:
@@ -91,6 +91,9 @@ class Profile(models.Model): | |||
last_account_flagged = models.DateTimeField(blank=True, null=True, db_index=True) | |||
num_email_forwarded_in_deleted_address = models.PositiveIntegerField(default=0) | |||
num_email_blocked_in_deleted_address = models.PositiveIntegerField(default=0) | |||
num_level_one_trackers_blocked_in_deleted_address = models.PositiveIntegerField( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New fields need to have null=True
to avoid data loss during deployment. The existing code will be running during the deployment, and will fail with NOT NULL constraint failed
when adding a new address or profile. You'll need to recreate the migration:
manage.py migrate emails 0048
rm emails/migrations/0049_*
manage.py makemigrations
Later, we can do a data migration to populate the nulls and a schema migration to disallow nulls.
Alternatively, you might be able to add a step to the migration to add a database-level default, something like ALTER TABLE "emails_domainaddress" ALTER COLUMN "num_level_one_trackers_blocked" SET DEFAULT 0
. I haven't tried that, I'm not sure how it will work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This worked, tested as:
- Apply the migration
- Check out
main
. I should check outv3.5.21
, butmain
should work too - Load the site, create a relay alias
Previously, it fails, because of the NOT NULL constraint failed
. Now it succeeds, and sets num_level_one_trackers_blocked
to NULL
.
However, the profile breaks when switching back to this branch, because it is attempting to add 0 and None
and failing. The code needs to interpret a None
as 0.
Maybe we'll try the database default next time...
@property | ||
def level_one_trackers_blocked(self): | ||
return ( | ||
sum(ra.num_level_one_trackers_blocked for ra in self.relay_addresses) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd need to check if Django converts a NULL
to a 0, if we make the fields nullable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not 🤦
emails/views.py
Outdated
else: | ||
tracker_report_link = "" | ||
|
||
if "has_num_level_one_email_trackers_removed" in request.GET: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the override could set the number directly:
if "has_num_level_one_email_trackers_removed" in request.GET: | |
if "num_level_one_email_trackers_removed" in request.GET: | |
num_level_one_email_trackers_removed = int(request.GET["num_level_one_email_trackers_removed"]) | |
else | |
num_level_one_email_trackers_removed = 0 |
emails/views.py
Outdated
<dd>{"Yes" if has_email_tracker_study_link else "No"}</dd> | ||
<dt>has_tracker_report_link</dt> | ||
<dd>{"Yes" if has_tracker_report_link else "No"}</dd> | ||
<dt>has_num_level_one_email_trackers_removed</dt> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: line up <dt>
s...
<dt>has_num_level_one_email_trackers_removed</dt> | |
<dt>has_num_level_one_email_trackers_removed</dt> |
emails/tests/utils_tests.py
Outdated
@@ -5,7 +5,7 @@ | |||
from django.test import TestCase, override_settings | |||
from unittest.mock import patch | |||
from model_bakery import baker | |||
from waffle.testutils import override_sample | |||
from waffle.testutils import override_flag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
override_flag
is not used in this file, please remove the import
emails/views.py
Outdated
else: | ||
has_tracker_report_link = False | ||
if has_tracker_report_link: | ||
tracker_report_link = "https://example.com/fake_tracker_report_link" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This could be a real report to test that as well:
tracker_report_link = "https://example.com/fake_tracker_report_link" | |
tracker_report_link = '/tracker-report/#{"sender": "sender@example.com", "received_at": 1658434657, "trackers": {"fake-tracker.example.com": 2}}' |
if "has_email_tracker_study_link" in request.GET: | ||
has_email_tracker_study_link = strtobool( | ||
request.GET["has_email_tracker_study_link"] | ||
if "has_tracker_report_link" in request.GET: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for updating the wrapped email template and test view!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding the test! I didn't know we added tests for this but the failed tests told me about it and was happy to add on to it!
to prevent errors during requests to the service during migrations
since it is not used while the email is relayed
and update email wrapper test to be more similar to actual data passed in context
and add more tests for tracker removal
which the frontend expects in instead of in seconds
so that we are using the lists as according to the license for Disconnect list as stated in shavar-prod-lists repo
until we can handle the CircleCI throwing FileNotFoundError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @say-yawn! This is OK for merging and deploying to stage, where usage is low, but the null
value for profiles and aliases created during the deploy will bite us in production.
@@ -11,6 +13,22 @@ | |||
logger = logging.getLogger("events") | |||
|
|||
|
|||
def get_trackers(category="Email"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed this a little in a different channel, and we're deferring live download of data until the a future PR. I'd rather this code not be added until that time, but I'll try to remember to review it then. Since the code is unused, these comments are not blockers.
I'm curious about the license terms, and if they require download at the time of use, or if we can download once as part of building the Docker image, or something else.
for _, resources in entity.items(): | ||
for _, domains in resources.items(): | ||
trackers.extend(domains) | ||
# when storing use a tuple so it's not mutable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this comment doesn't appear to reflect the code, since the data is returned as a list instead of a tuple.
@@ -34,6 +52,16 @@ def __init__(self, app_name, app_module): | |||
self.badwords = self._load_terms("badwords.txt") | |||
self.blocklist = self._load_terms("blocklist.txt") | |||
|
|||
# TODO: fix the relative path issue on CircleCI to use the commented code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocker: I believe the issue is that the folder emails/tracker_lists
does not exist. It can be created on the fly, or we can create a file like emails/tracker_lists/.keep
or emails/tracker_lists/README.md
to ensure the directory exists.
@property | ||
def level_one_trackers_blocked(self): | ||
return ( | ||
sum(ra.num_level_one_trackers_blocked for ra in self.relay_addresses) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not 🤦
sum(ra.num_level_one_trackers_blocked for ra in self.relay_addresses) | ||
+ sum(da.num_level_one_trackers_blocked for da in self.domain_addresses) | ||
+ self.num_level_one_trackers_blocked_in_deleted_address |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because these can be null
, we need something like:
sum(ra.num_level_one_trackers_blocked for ra in self.relay_addresses) | |
+ sum(da.num_level_one_trackers_blocked for da in self.domain_addresses) | |
+ self.num_level_one_trackers_blocked_in_deleted_address | |
sum(ra.num_level_one_trackers_blocked or 0 for ra in self.relay_addresses) | |
+ sum(da.num_level_one_trackers_blocked or 0 for da in self.domain_addresses) | |
+ self.num_level_one_trackers_blocked_in_deleted_address or 0 |
profile.num_level_one_trackers_blocked_in_deleted_address += ( | ||
self.num_level_one_trackers_blocked | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, either could be null
:
profile.num_level_one_trackers_blocked_in_deleted_address += ( | |
self.num_level_one_trackers_blocked | |
) | |
profile.num_level_one_trackers_blocked_in_deleted_address = ( | |
(profile.num_level_one_trackers_blocked_in_deleted_address or 0) + | |
(self.num_level_one_trackers_blocked or 0) | |
) |
@@ -5,7 +5,6 @@ | |||
from django.test import TestCase, override_settings | |||
from unittest.mock import patch | |||
from model_bakery import baker | |||
from waffle.testutils import override_sample |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch!
general_removed = tracker_details["tracker_removed"] | ||
general_count = tracker_details["level_one"]["count"] | ||
|
||
assert changed_content == "<a href='https://test.com/faq'>A link</a>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I like checking the expected content
@@ -373,12 +373,17 @@ def set_user_group(user): | |||
internal_group.user_set.add(user) | |||
|
|||
|
|||
def convert_domains_to_regex_patterns(domain_pattern): | |||
return r"""(["'])(\S*://(\S*\.)*""" + re.escape(domain_pattern) + r"\S*)\1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 pyflakes is happy!
) | ||
address.num_level_one_trackers_blocked += removed_count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here:
address.num_level_one_trackers_blocked += removed_count | |
address.num_level_one_trackers_blocked = ( | |
(address.num_level_one_trackers_blocked or 0) + removed_count | |
) |
New feature description
Migration 0049 on Emails app to store number of tracker removed for Relay and Domain addresses, and for deleted addresses on the Profile object
Add field for tracker removed count per Relay and Domain addresses
Return tracker removed count per Relay and Domain addresses on the relayaddresses and domainaddresses API endpoint under the
num_level_one_trackers_blocked
fieldAdd field for tracker removed count for deleted addresses on the profile object
Return aggregate tracked removed per profile on the profile API endpoint under
level_one_trackers_blocked
fieldUse profile setting value under
remove_level_one_email_trackers
on Profile to remove email trackers in level one categoryGenerate report link for tracker removed with data stored in URL's hash bit so that it does not get logged
Pass report link and tracker removed count for Relay header/footer wrapper for related emails
Screenshot (if applicable)
Not applicable.
How to test
(Prerequisite) Email tracker removal is behind the
tracker_removal
flag which needs to be set by:and because Django-Waffle's logic for checking it the flag is enabled for user (method is_active_for_user) does not check the
everyone
we need to use theauthenticated
.Check that emails relayed with user's setting DISABLED (default) for remove email trackers relays emails with trackers NOT removed
/accounts/settings/
Remove email trackers in all forwarded emails.
is NOT checkedCheck that emails relayed with user's setting ENABLED for remove email trackers relays emails with trackers removed
/accounts/settings/
Remove email trackers in all forwarded emails.
and click "Save" button on the bottom right corner/accounts/profile/
and check that a new column forTrackers Removed
is added to the address card like:4. Send an email with trackers to your alias. Sign-up for activecampaign.com is a good sample email with trackers. 5. Check that email is relayed with trackers removed count and "Learn more" link on the header of the relayed email like:
6. Click on "Learn more" and check that number of trackers removed count in the generated report is the same as the tracker removed count on the email header. 7. Go back to the `/accounts/profile` and check that the count for `Trackers Removed` on the address card increased
Check that emails relayed with user's setting ENABLED then DISABLED for remove email trackers relays emails with trackers NOT removed
Remove email trackers in all forwarded emails.
and click "Save" button on the bottom right corner/accounts/profile/
and check that the column forTrackers Removed
is greyed out on the address card like:Screen Shot 2022-07-21 at 21.45.31
/accounts/profile
and check that the count forTrackers Removed
stayed the sameChecklist
/frontend/src/styles/tokens.scss
).