Skip to content

Commit 43e5c16

Browse files
committed
feat(releases): Add email notification on commit fetch failure
1 parent f993270 commit 43e5c16

File tree

8 files changed

+229
-177
lines changed

8 files changed

+229
-177
lines changed

src/sentry/tasks/commits.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,26 @@ def generate_invalid_identity_email(identity, commit_failure=False):
2222
}
2323

2424
return MessageBuilder(
25-
subject='Action Required',
25+
subject='Unable to Fetch Commits' if commit_failure else 'Action Required',
2626
context=new_context,
2727
template='sentry/emails/identity-invalid.txt',
2828
html_template='sentry/emails/identity-invalid.html',
2929
)
3030

31+
32+
def generate_fetch_commits_error_email(release, error_message):
33+
new_context = {
34+
'release': release,
35+
'error_message': error_message,
36+
}
37+
38+
return MessageBuilder(
39+
subject='Unable to Fetch Commits',
40+
context=new_context,
41+
template='sentry/emails/unable-to-fetch-commits.txt',
42+
html_template='sentry/emails/unable-to-fetch-commits.html',
43+
)
44+
3145
# we're future proofing this function a bit so it could be used with other code
3246

3347

@@ -107,7 +121,7 @@ def fetch_commits(release_id, user_id, refs, prev_release_id=None, **kwargs):
107121
repo_commits = provider.compare_commits(repo, start_sha, end_sha, actor=user)
108122
except NotImplementedError:
109123
pass
110-
except (PluginError, InvalidIdentity) as exc:
124+
except Exception as exc:
111125
logger.exception(
112126
'fetch_commits.error',
113127
exc_info=True,
@@ -121,6 +135,13 @@ def fetch_commits(release_id, user_id, refs, prev_release_id=None, **kwargs):
121135
)
122136
if isinstance(exc, InvalidIdentity) and getattr(exc, 'identity', None):
123137
handle_invalid_identity(identity=exc.identity, commit_failure=True)
138+
elif isinstance(exc, (PluginError, InvalidIdentity)):
139+
msg = generate_fetch_commits_error_email(release, exc.message)
140+
msg.send_async(to=[user.email])
141+
else:
142+
msg = generate_fetch_commits_error_email(
143+
release, 'An internal system error occurred.')
144+
msg.send_async(to=[user.email])
124145
else:
125146
logger.info(
126147
'fetch_commits.complete',

src/sentry/templates/sentry/debug/mail/preview.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
<option value="mail/recover-account/">Reset Password</option>
2828
<option value="mail/invalid-identity/">Invalid Identity</option>
2929
</optgroup>
30+
<optgroup label="Releases">
31+
<option value="mail/unable-to-fetch-commits/">Unable to Fetch Commits</option>
32+
</optgroup>
3033
<optgroup label="Membership">
3134
<option value="mail/request-access/">Access Requested</option>
3235
<option value="mail/access-approved/">Access Approved</option>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "sentry/emails/base.html" %}
2+
3+
{% block main %}
4+
<h3>Unable to Fetch Commits</h3>
5+
<p>We were unable to fetch the commit log for your release (<em>{{ release.version }}</em>) due to the following error:</p>
6+
<p>{{ error_message }}</p>
7+
{% endblock %}
8+
9+
{% block footer %}{% endblock %}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Unable to Fetch Commits
2+
-----------------------
3+
4+
We were unable to fetch the commit log for your release ({{ release.version }}) due to the following error:
5+
6+
{{ error_message }}

src/sentry/web/debug_urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from sentry.web.frontend.debug.debug_resolved_in_release_email import (
2424
DebugResolvedInReleaseEmailView, DebugResolvedInReleaseUpcomingEmailView
2525
)
26+
from sentry.web.frontend.debug.debug_unable_to_fetch_commits_email import DebugUnableToFetchCommitsEmailView
2627
from sentry.web.frontend.debug.debug_unassigned_email import (DebugUnassignedEmailView)
2728
from sentry.web.frontend.debug.debug_new_processing_issues_email import (
2829
DebugNewProcessingIssuesEmailView,
@@ -57,6 +58,7 @@
5758
url(r'^debug/mail/invalid-identity/$', DebugInvalidIdentityEmailView.as_view()),
5859
url(r'^debug/mail/confirm-email/$', sentry.web.frontend.debug.mail.confirm_email),
5960
url(r'^debug/mail/recover-account/$', sentry.web.frontend.debug.mail.recover_account),
61+
url(r'^debug/mail/unable-to-fetch-commits/$', DebugUnableToFetchCommitsEmailView.as_view()),
6062
url(r'^debug/mail/unassigned/$', DebugUnassignedEmailView.as_view()),
6163
url(r'^debug/mail/org-delete-confirm/$', sentry.web.frontend.debug.mail.org_delete_confirm),
6264
url(r'^debug/mail/mfa-removed/$', DebugMfaRemovedEmailView.as_view()),
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from __future__ import absolute_import, print_function
2+
3+
from django.views.generic import View
4+
5+
from sentry.models import Release
6+
from sentry.tasks.commits import generate_fetch_commits_error_email
7+
8+
from .mail import MailPreview
9+
10+
11+
class DebugUnableToFetchCommitsEmailView(View):
12+
def get(self, request):
13+
release = Release(version='abcdef')
14+
15+
email = generate_fetch_commits_error_email(
16+
release,
17+
'An internal server error occurred'
18+
)
19+
return MailPreview(
20+
html_template=email.html_template,
21+
text_template=email.template,
22+
context=email.context,
23+
).render(request)

tests/acceptance/test_emails.py

Lines changed: 30 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@
44

55
from sentry.testutils import AcceptanceTestCase
66

7+
EMAILS = (
8+
('/debug/mail/assigned/', 'assigned'),
9+
('/debug/mail/assigned/self/', 'assigned self'),
10+
('/debug/mail/note/', 'note'),
11+
('/debug/mail/regression/', 'regression'),
12+
('/debug/mail/regression/release/', 'regression with version'),
13+
('/debug/mail/new-release/', 'release'),
14+
('/debug/mail/resolved/', 'resolved'),
15+
('/debug/mail/resolved-in-release/', 'resolved in release'),
16+
('/debug/mail/resolved-in-release/upcoming/', 'resolved in release upcoming'),
17+
('/debug/mail/unassigned/', 'unassigned'),
18+
('/debug/mail/unable-to-fetch-commits/', 'unable to fetch commits'),
19+
('/debug/mail/alert/', 'alert'),
20+
('/debug/mail/digest/', 'digest'),
21+
('/debug/mail/invalid-identity/', 'invalid identity'),
22+
('/debug/mail/invitation/', 'invitation'),
23+
('/debug/mail/report/', 'report'),
24+
('/debug/mail/mfa-added/', 'mfa added'),
25+
('/debug/mail/mfa-removed/', 'mfa removed'),
26+
('/debug/mail/password-changed/', 'password changed'),
27+
)
28+
729

830
class EmailTestCase(AcceptanceTestCase):
931
def setUp(self):
@@ -20,177 +42,12 @@ def build_url(self, path, format='html'):
2042
}),
2143
)
2244

23-
def test_assigned_html(self):
24-
self.browser.get(self.build_url('/debug/mail/assigned/'))
25-
self.browser.wait_until('#preview')
26-
self.browser.snapshot('assigned email html')
27-
28-
def test_assigned_txt(self):
29-
self.browser.get(self.build_url('/debug/mail/assigned/', 'txt'))
30-
self.browser.wait_until('#preview')
31-
self.browser.snapshot('assigned email txt')
32-
33-
def test_assigned_self_html(self):
34-
self.browser.get(self.build_url('/debug/mail/assigned/self/'))
35-
self.browser.wait_until('#preview')
36-
self.browser.snapshot('assigned_self email html')
37-
38-
def test_assigned_self_txt(self):
39-
self.browser.get(self.build_url('/debug/mail/assigned/self/', 'txt'))
40-
self.browser.wait_until('#preview')
41-
self.browser.snapshot('assigned_self email txt')
42-
43-
def test_note_html(self):
44-
self.browser.get(self.build_url('/debug/mail/note/'))
45-
self.browser.wait_until('#preview')
46-
self.browser.snapshot('note email html')
47-
48-
def test_note_txt(self):
49-
self.browser.get(self.build_url('/debug/mail/note/', 'txt'))
50-
self.browser.wait_until('#preview')
51-
self.browser.snapshot('note email txt')
52-
53-
def test_regression_html(self):
54-
self.browser.get(self.build_url('/debug/mail/regression/'))
55-
self.browser.wait_until('#preview')
56-
self.browser.snapshot('regression email html')
57-
58-
def test_regression_txt(self):
59-
self.browser.get(self.build_url('/debug/mail/regression/', 'txt'))
60-
self.browser.wait_until('#preview')
61-
self.browser.snapshot('regression email txt')
62-
63-
def test_regression_with_version_html(self):
64-
self.browser.get(self.build_url('/debug/mail/regression/release/'))
65-
self.browser.wait_until('#preview')
66-
self.browser.snapshot('regression_with_version email html')
67-
68-
def test_regression_with_version_txt(self):
69-
self.browser.get(self.build_url('/debug/mail/regression/release/', 'txt'))
70-
self.browser.wait_until('#preview')
71-
self.browser.snapshot('regression_with_version email txt')
72-
73-
def test_release_html(self):
74-
self.browser.get(self.build_url('/debug/mail/new-release/'))
75-
self.browser.wait_until('#preview')
76-
self.browser.snapshot('release email html')
77-
78-
def test_release_txt(self):
79-
self.browser.get(self.build_url('/debug/mail/new-release/', 'txt'))
80-
self.browser.wait_until('#preview')
81-
self.browser.snapshot('release email txt')
82-
83-
def test_resolved_html(self):
84-
self.browser.get(self.build_url('/debug/mail/resolved/'))
85-
self.browser.wait_until('#preview')
86-
self.browser.snapshot('resolved email html')
87-
88-
def test_resolved_txt(self):
89-
self.browser.get(self.build_url('/debug/mail/resolved/', 'txt'))
90-
self.browser.wait_until('#preview')
91-
self.browser.snapshot('resolved email txt')
92-
93-
def test_resolved_in_release_html(self):
94-
self.browser.get(self.build_url('/debug/mail/resolved-in-release/'))
95-
self.browser.wait_until('#preview')
96-
self.browser.snapshot('resolved_in_release email html')
97-
98-
def test_resolved_in_release_txt(self):
99-
self.browser.get(self.build_url('/debug/mail/resolved-in-release/', 'txt'))
100-
self.browser.wait_until('#preview')
101-
self.browser.snapshot('resolved_in_release email txt')
102-
103-
def test_resolved_in_release_upcoming_html(self):
104-
self.browser.get(self.build_url('/debug/mail/resolved-in-release/upcoming/'))
105-
self.browser.wait_until('#preview')
106-
self.browser.snapshot('resolved_in_release_upcoming email html')
107-
108-
def test_resolved_in_release_upcoming_txt(self):
109-
self.browser.get(self.build_url('/debug/mail/resolved-in-release/upcoming/', 'txt'))
110-
self.browser.wait_until('#preview')
111-
self.browser.snapshot('resolved_in_release_upcoming email txt')
112-
113-
def test_unassigned_html(self):
114-
self.browser.get(self.build_url('/debug/mail/unassigned/'))
115-
self.browser.wait_until('#preview')
116-
self.browser.snapshot('unassigned email html')
117-
118-
def test_unassigned_txt(self):
119-
self.browser.get(self.build_url('/debug/mail/unassigned/', 'txt'))
120-
self.browser.wait_until('#preview')
121-
self.browser.snapshot('unassigned email txt')
122-
123-
def test_new_event_html(self):
124-
self.browser.get(self.build_url('/debug/mail/alert/'))
125-
self.browser.wait_until('#preview')
126-
self.browser.snapshot('new event email html')
127-
128-
def test_new_event_txt(self):
129-
self.browser.get(self.build_url('/debug/mail/alert/', 'txt'))
130-
self.browser.wait_until('#preview')
131-
self.browser.snapshot('new event email txt')
132-
133-
def test_digest_html(self):
134-
self.browser.get(self.build_url('/debug/mail/digest/'))
135-
self.browser.wait_until('#preview')
136-
self.browser.snapshot('digest email html')
137-
138-
def test_digest_txt(self):
139-
self.browser.get(self.build_url('/debug/mail/digest/', 'txt'))
140-
self.browser.wait_until('#preview')
141-
self.browser.snapshot('digest email txt')
142-
143-
def test_invalid_identity_text(self):
144-
self.browser.get(self.build_url('/debug/mail/invalid-identity/', 'txt'))
145-
self.browser.wait_until('#preview')
146-
self.browser.snapshot('invalid identity email txt')
147-
148-
def test_invalid_identity_html(self):
149-
self.browser.get(self.build_url('/debug/mail/invalid-identity/', 'html'))
150-
self.browser.wait_until('#preview')
151-
self.browser.snapshot('invalid identity email html')
152-
153-
def test_invitation_text(self):
154-
self.browser.get(self.build_url('/debug/mail/invitation/', 'txt'))
155-
self.browser.wait_until('#preview')
156-
self.browser.snapshot('invitation email txt')
157-
158-
def test_invitation_html(self):
159-
self.browser.get(self.build_url('/debug/mail/invitation/', 'html'))
160-
self.browser.wait_until('#preview')
161-
self.browser.snapshot('invitation email html')
162-
163-
def test_report_html(self):
164-
self.browser.get(self.build_url('/debug/mail/report/'))
165-
self.browser.wait_until('#preview')
166-
self.browser.snapshot('report email html')
167-
168-
def test_mfa_added_html(self):
169-
self.browser.get(self.build_url('/debug/mail/mfa-added/'))
170-
self.browser.wait_until('#preview')
171-
self.browser.snapshot('mfa added email html')
172-
173-
def test_mfa_added_txt(self):
174-
self.browser.get(self.build_url('/debug/mail/mfa-added/', 'txt'))
175-
self.browser.wait_until('#preview')
176-
self.browser.snapshot('mfa added email txt')
177-
178-
def test_mfa_removed_html(self):
179-
self.browser.get(self.build_url('/debug/mail/mfa-removed/'))
180-
self.browser.wait_until('#preview')
181-
self.browser.snapshot('mfa removed email html')
182-
183-
def test_mfa_removed_text(self):
184-
self.browser.get(self.build_url('/debug/mail/mfa-removed/', 'txt'))
185-
self.browser.wait_until('#preview')
186-
self.browser.snapshot('mfa removed email txt')
187-
188-
def test_password_changed_html(self):
189-
self.browser.get(self.build_url('/debug/mail/password-changed/'))
190-
self.browser.wait_until('#preview')
191-
self.browser.snapshot('password changed email html')
45+
def test_emails(self):
46+
for url, name in EMAILS:
47+
self.browser.get(self.build_url(url, 'html'))
48+
self.browser.wait_until('#preview')
49+
self.browser.snapshot('{} email html'.format(name))
19250

193-
def test_password_changed_text(self):
194-
self.browser.get(self.build_url('/debug/mail/password-changed/', 'txt'))
195-
self.browser.wait_until('#preview')
196-
self.browser.snapshot('password changed email txt')
51+
self.browser.get(self.build_url(url, 'txt'))
52+
self.browser.wait_until('#preview')
53+
self.browser.snapshot('{} email txt'.format(name))

0 commit comments

Comments
 (0)