Skip to content

Commit c84ad30

Browse files
committed
Initial rollout of Sentry integration with 404/500’s
This adds a new Error Page promo type, and adds the data into the error page rendering. It also adds the Sentry “User Feedback” UI to our 500 pages. I’m not 100% sold on this UI, but it’s pretty neat to get user feedback. I wish it was a bit more configurable. This isn’t using the JS library currently, so it is hard-coding the promo text. I should probably enable injecting dynamic copy for this over time, but this seems fine for now.
1 parent c8254c9 commit c84ad30

File tree

7 files changed

+100
-46
lines changed

7 files changed

+100
-46
lines changed

readthedocs/core/urls/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
views.random_page,
4646
name='random_page'),
4747
url(r'^random/$', views.random_page, name='random_page'),
48+
url(r'^404/$', 'readthedocs.core.views.server_error_404'),
49+
# For testing the 500's with DEBUG on.
50+
# url(r'^500/$', 'readthedocs.core.views.server_error'),
4851
url(r'^500/$', views.divide_by_zero, name='divide_by_zero'),
4952
url((r'^wipe/(?P<project_slug>{project_slug})/'
5053
r'(?P<version_slug>{version_slug})/$'.format(**pattern_opts)),

readthedocs/core/views/__init__.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from readthedocs.builds.models import Build
1919
from readthedocs.builds.models import Version
2020
from readthedocs.core.utils import broadcast
21+
from readthedocs.donate.models import SupporterPromo
22+
from readthedocs.donate.utils import offer_promo
2123
from readthedocs.projects import constants
2224
from readthedocs.projects.models import Project, ImportedFile
2325
from readthedocs.projects.tasks import remove_dir
@@ -104,20 +106,38 @@ def divide_by_zero(request):
104106
return 1 / 0
105107

106108

107-
def server_error(request, exception, template_name='500.html'):
109+
def add_promo_data(display_type):
110+
promo_queryset = SupporterPromo.objects.filter(live=True, display_type=display_type)
111+
promo_obj = promo_queryset.order_by('?').first()
112+
if promo_obj:
113+
promo_dict = offer_promo(promo_obj=promo_obj, project=None)
114+
else:
115+
promo_dict = None
116+
return promo_dict
117+
118+
119+
def server_error(request, template_name='500.html', **kwargs):
108120
"""A simple 500 handler so we get media"""
121+
promo_dict = add_promo_data(display_type='error')
109122
r = render_to_response(template_name,
110-
context_instance=RequestContext(request))
123+
context_instance=RequestContext(request),
124+
context={
125+
'promo_data': promo_dict,
126+
})
111127
r.status_code = 500
112128
return r
113129

114130

115-
def server_error_404(request, exception, template_name='404.html'):
131+
def server_error_404(request, template_name='404.html', **kwargs):
116132
"""A simple 404 handler so we get media"""
133+
promo_dict = add_promo_data(display_type='error')
117134
response = get_redirect_response(request, path=request.get_full_path())
118135
if response:
119136
return response
120137
r = render_to_response(template_name,
121-
context_instance=RequestContext(request))
138+
context_instance=RequestContext(request),
139+
context={
140+
'promo_data': promo_dict,
141+
})
122142
r.status_code = 404
123143
return r

readthedocs/donate/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
('doc', 'Documentation Pages'),
1616
('site-footer', 'Site Footer'),
1717
('search', 'Search Pages'),
18+
('error', 'Error Pages'),
1819
)
1920

2021
INCLUDE = 'include'

readthedocs/donate/signals.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from django.dispatch import receiver
44
from django.conf import settings
5-
from django.core.cache import cache
65

76
from readthedocs.restapi.signals import footer_response
8-
from readthedocs.donate.models import SupporterPromo, VIEWS, CLICKS, OFFERS, INCLUDE, EXCLUDE
7+
from readthedocs.donate.models import SupporterPromo, INCLUDE, EXCLUDE
8+
from readthedocs.donate.utils import offer_promo
99

1010

1111
PROMO_GEO_PATH = getattr(settings, 'PROMO_GEO_PATH', None)
@@ -189,23 +189,8 @@ def attach_promo_data(sender, **kwargs):
189189
show_promo = False
190190

191191
if show_promo:
192-
promo_dict = promo_obj.as_dict()
192+
promo_dict = offer_promo(promo_obj=promo_obj, project=project)
193193
resp_data['promo_data'] = promo_dict
194-
promo_obj.incr(OFFERS)
195-
promo_obj.incr(OFFERS, project=project)
196-
# Set validation cache
197-
for type in [VIEWS, CLICKS]:
198-
cache.set(
199-
promo_obj.cache_key(type=type, hash=promo_dict['hash']),
200-
0, # Number of times used. Make this an int so we can detect multiple uses
201-
60 * 60 # hour
202-
)
203-
# Set project for hash key, so we can count it later.
204-
cache.set(
205-
promo_obj.cache_key(type='project', hash=promo_dict['hash']),
206-
project.slug,
207-
60 * 60 # hour
208-
)
209194

210195
# Set promo object on return JSON
211196
resp_data['promo'] = show_promo

readthedocs/donate/utils.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import pytz
22
import datetime
33

4+
from django.core.cache import cache
5+
6+
OFFERS = 'offers'
7+
VIEWS = 'views'
8+
CLICKS = 'clicks'
9+
410

511
def get_ad_day():
612
date = pytz.utc.localize(datetime.datetime.utcnow())
@@ -11,3 +17,32 @@ def get_ad_day():
1117
tzinfo=pytz.utc,
1218
)
1319
return day
20+
21+
22+
def offer_promo(promo_obj, project=None):
23+
"""
24+
Do the book keeping required to track promo offers.
25+
26+
This generated a hash as part of the return dict,
27+
so that must be used throughout the processing pipeline in order to dedupe clicks.
28+
"""
29+
30+
promo_dict = promo_obj.as_dict()
31+
promo_obj.incr(OFFERS)
32+
# Set validation cache
33+
for promo_type in [VIEWS, CLICKS]:
34+
cache.set(
35+
promo_obj.cache_key(type=promo_type, hash=promo_dict['hash']),
36+
0, # Number of times used. Make this an int so we can detect multiple uses
37+
60 * 60 # hour
38+
)
39+
40+
if project:
41+
# Set project for hash key, so we can count it later.
42+
promo_obj.incr(OFFERS, project=project)
43+
cache.set(
44+
promo_obj.cache_key(type='project', hash=promo_dict['hash']),
45+
project.slug,
46+
60 * 60 # hour
47+
)
48+
return promo_dict

readthedocs/templates/404.html

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,16 @@ <h1>You've found something that doesn't exist.</h1>
2929
{% endifequal %}
3030
</div>
3131
{% endif %}
32-
<pre style="line-height: 1.25; white-space: pre;">
3332

34-
\ SORRY /
35-
\ /
36-
\ This page does /
37-
] not exist yet. [ ,'|
38-
] [ / |
39-
]___ ___[ ,' |
40-
] ]\ /[ [ |: |
41-
] ] \ / [ [ |: |
42-
] ] ] [ [ [ |: |
43-
] ] ]__ __[ [ [ |: |
44-
] ] ] ]\ _ /[ [ [ [ |: |
45-
] ] ] ] (#) [ [ [ [ :===='
46-
] ] ]_].nHn.[_[ [ [
47-
] ] ] HHHHH. [ [ [
48-
] ] / `HH("N \ [ [
49-
]__]/ HHH " \[__[
50-
] NNN [
51-
] N/" [
52-
] N H [
53-
/ N \
54-
/ q, \
55-
/ \
56-
</pre>
33+
<h1>
34+
Page Not Found!
35+
</h1>
36+
37+
<a href="{{ promo_data.link }}">
38+
<img src="{{ promo_data.image }}" width="75%" />
39+
</a>
40+
41+
<p>
42+
Read the Docs is sponsored by <a href="{{ promo_data.link }}">Sentry</a>, which gives developers the tools to keep calm when things catch fire.
43+
</p>
5744
{% endblock %}

readthedocs/templates/500.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
{% endblock %}
1111

1212
{% block content %}
13+
<h3>
14+
Server Error
15+
</h3>
16+
1317
<pre style="line-height: 1.25; white-space: pre;">
1418
.
1519
":"
@@ -21,4 +25,23 @@
2125
Fail. Check back in a bit!
2226

2327
</pre>
28+
29+
<!-- Sentry JS SDK 2.1.+ required -->
30+
<script src="https://cdn.ravenjs.com/2.3.0/raven.min.js"></script>
31+
32+
{% if request.sentry.id %}
33+
<script>
34+
Raven.showReportDialog({
35+
eventId: '{{ request.sentry.id }}',
36+
37+
// use the public DSN (dont include your secret!)
38+
dsn: 'https://[email protected]/148442'
39+
});
40+
</script>
41+
{% endif %}
42+
43+
<p>
44+
Read the Docs is sponsored by <a href="{{ promo_data.link }}">Sentry</a>, which gives developers the tools to keep calm when things catch fire.
45+
</p>
46+
2447
{% endblock %}

0 commit comments

Comments
 (0)