diff --git a/events/ua_stuff.py b/events/ua_stuff.py
new file mode 100644
index 0000000..02e8a96
--- /dev/null
+++ b/events/ua_stuff.py
@@ -0,0 +1,45 @@
+from user_agents import parse as ua_parse
+
+
+def enrich_contexts_with_ua(parsed_data):
+ # GlitchTip has some mechanism to get "synthetic" (i.e. not present in the original, UA-header derived) info into
+ # first the contexts, which is then propagated (with a whole bunch of other info from contexts) to the tags. Both
+ # these steps happen on-digest.
+ #
+ # I'm not sure what I want myself... on the one hand I'm not a fan of this kind of magic. On the other: if the data
+ # usually lives in the "context" (e.g. in the JS world), you might as well synthesize it into that location when iit
+ # is not in that location in the data (but is available through other means).
+ #
+ # my set of samples has very little data for contexts, so it's hard to get a feel for it. I imagine that having this
+ # info available in tags can be useful for searching, or for getting a quick feel of the data (tags show up in the
+ # RHS of various screens). But again: too little data to tell yet. Add to that that I'm much less inclined than my
+ # competitors to give OS/browser info the main stage (icons? yuck!). So we'll just parse it, put it "somewhere", and
+ # look at it again "later".
+ contexts = parsed_data.get("contexts", {})
+
+ ua_string = (parsed_data.get("request", {}).get("headers", {})).get("User-Agent")
+ if ua_string is None:
+ return contexts
+
+ user_agent = ua_parse(ua_string)
+
+ if "browser" not in contexts:
+ contexts["browser"] = {
+ "name": user_agent.browser.family,
+ "version": user_agent.browser.version_string,
+ }
+
+ if "os" not in contexts:
+ contexts["os"] = {
+ "name": user_agent.os.family,
+ "version": user_agent.os.version_string,
+ }
+
+ if "device" not in contexts:
+ contexts["device"] = {
+ "family": user_agent.device.family,
+ "model": user_agent.device.model,
+ "brand": user_agent.device.brand,
+ }
+
+ return contexts
diff --git a/issues/templates/issues/event_details.html b/issues/templates/issues/event_details.html
index 0e445ce..ad1c70f 100644
--- a/issues/templates/issues/event_details.html
+++ b/issues/templates/issues/event_details.html
@@ -73,7 +73,6 @@
{% endif %}
{% if parsed_data.request %}
-
@@ -91,15 +90,12 @@
{% if parsed_data.request.headers %}
REQUEST HEADERS
- {# TODO the user's browser and OS can be deduced from the request headers. Perhaps that info should go near the headers #}
-
{% for key, value in parsed_data.request.headers.items %}
{% endfor %}
-
{% endif %} {# end if parsed_data.request.headers #}
@@ -121,6 +117,34 @@
{% endif %}
+{% if contexts %}
+
+
+
+ {% for context_key, context in contexts|items %}
+
{{ context_key|upper }}
+ {% for key, value in context|items %}
+
+
{{ key }}
+
{{ value }}
+
+ {% endfor %}
+ {% endfor %}
+
+{% endif %}
+
+{% comment %}
+earlier I said about "tracing": I don't believe much in this whole business of tracing, so I'm not going to display the associated data either
+
+now that we "just display all contexts" this is no longer true... some of the feeling persists, but I don't think
+that I'm so much anti-tracing that I want specifically exclude it from a generic loop. The data's there, let's just
+show it (in a non-special way)
+{% endcomment %}
+
+{% comment %}
+commented-out like it's 1999.
+this is now part of the more general "contexts" handling right above this section.
+the fact that we commented-out rather than clobbered reveals a small amount of doubt about whether this is the way.
{% if parsed_data.contexts.runtime %}
{# sentry gives this prime location (even a picture)... but why... it's kinda obvious what you're working in right? Maybe I could put it at the top of the modules list instead. And check if there's any other relevant info in that runtime context (RTFM) #}
@@ -135,6 +159,7 @@
{% endfor %}
{% endif %}
+{% endcomment %}
{% if parsed_data.modules %}
@@ -177,21 +202,5 @@
{% endif %}
-{% comment %}
-{# I don't believe much in this whole business of tracing, so I'm not going to display the associated data either #}
-{% if parsed_data.contexts.trace %}
-
-
-
- {% for key, value in parsed_data.contexts.trace|items %}
-
-
{{ key }}
-
{{ value }}
-
- {% endfor %}
-
-{% endif %}
-{% endcomment %}
-
{% endblock %}
diff --git a/issues/views.py b/issues/views.py
index 1335db8..9e43747 100644
--- a/issues/views.py
+++ b/issues/views.py
@@ -16,6 +16,8 @@ from bugsink.transaction import durable_atomic
from bugsink.period_utils import add_periods_to_datetime
from events.models import Event
+from events.ua_stuff import enrich_contexts_with_ua
+
from compat.timestamp import format_timestamp
from projects.models import ProjectMembership
@@ -409,6 +411,8 @@ def issue_event_details(request, issue, event_pk=None, digest_order=None, nav=No
([("environment", parsed_data["environment"])] if "environment" in parsed_data else []) + \
([("server_name", parsed_data["server_name"])] if "server_name" in parsed_data else [])
+ contexts = enrich_contexts_with_ua(parsed_data)
+
return render(request, "issues/event_details.html", {
"tab": "event-details",
"this_view": "event_details",
@@ -419,6 +423,7 @@ def issue_event_details(request, issue, event_pk=None, digest_order=None, nav=No
"parsed_data": parsed_data,
"key_info": key_info,
"deployment_info": deployment_info,
+ "contexts": contexts,
"mute_options": GLOBAL_MUTE_OPTIONS,
})
diff --git a/requirements.txt b/requirements.txt
index 7f29e24..cbffbc5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,3 +12,4 @@ python-dateutil
whitenoise
requests # for sentry-sdk-extensions, which is loaded in non-dev setup too
monofy
+user_agents