Breadcrumb timestamps: display harmonized w/ rest of application

in the correct timezone, with smaller milis

According to the spec, this should work because:

> The timestamp of the breadcrumb. Recommended. A timestamp representing when
> the breadcrumb occurred. The format is either a string as defined in [RFC
> 3339](https://tools.ietf.org/html/rfc3339) or a numeric (integer or float)
> value representing the number of seconds that have elapsed since the [Unix
> epoch](https://en.wikipedia.org/wiki/Unix_time). Breadcrumbs are most useful
> when they include a timestamp, as it creates a timeline leading up to an
> event.
This commit is contained in:
Klaas van Schelven
2025-07-28 10:24:36 +02:00
parent 13226603ec
commit ceca12940b
3 changed files with 37 additions and 13 deletions

View File

@@ -49,8 +49,7 @@
{{ breadcrumb.message }}
</td>
<td class="p-4 font-bold text-slate-500 dark:text-slate-300 align-top">
{{ breadcrumb.timestamp }}
{# {{ breadcrumb.timestamp|date:"G:i T" and milis }} #}
{{ breadcrumb.timestamp|timestamp_with_millis }}
</td>
</tr>

View File

@@ -7,8 +7,6 @@ from django.db.models import Q
from django.utils import timezone
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponseRedirect, HttpResponseNotAllowed
from django.utils.safestring import mark_safe
from django.template.defaultfilters import date
from django.urls import reverse
from django.core.exceptions import PermissionDenied
from django.http import Http404
@@ -31,6 +29,7 @@ from events.ua_stuff import get_contexts_enriched_with_ua
from compat.timestamp import format_timestamp
from projects.models import ProjectMembership
from tags.search import search_issues, search_events, search_events_optimized
from theme.templatetags.issues import timestamp_with_millis
from .models import Issue, IssueQuerysetStateManager, IssueStateManager, TurningPoint, TurningPointKind
from .forms import CommentForm
@@ -555,12 +554,6 @@ def issue_event_breadcrumbs(request, issue, event_pk=None, digest_order=None, na
})
def _date_with_milis_html(timestamp):
return mark_safe(
date(timestamp, "j M G:i:s") + "." +
'<span class="text-xs">' + date(timestamp, "u")[:3] + '</span>')
def _first_last(qs_with_digest_order):
# this was once implemented with Min/Max, but just doing 2 queries is (on sqlite at least) much faster.
first = qs_with_digest_order.order_by("digest_order").values_list("digest_order", flat=True).first()
@@ -604,9 +597,9 @@ def issue_event_details(request, issue, event_pk=None, digest_order=None, nav=No
("mechanism", get_path(get_main_exception(parsed_data), "mechanism", "type")),
("issue_id", issue.id),
("timestamp", _date_with_milis_html(event.timestamp)),
("ingested at", _date_with_milis_html(event.ingested_at)),
("digested at", _date_with_milis_html(event.digested_at)),
("timestamp", timestamp_with_millis(event.timestamp)),
("ingested at", timestamp_with_millis(event.ingested_at)),
("digested at", timestamp_with_millis(event.digested_at)),
("digest order", event.digest_order),
("remote_addr", event.remote_addr),
]

View File

@@ -1,3 +1,4 @@
from datetime import datetime
import re
from django import template
from pygments import highlight
@@ -5,6 +6,9 @@ from pygments.formatters import HtmlFormatter
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.template.defaultfilters import date
from compat.timestamp import parse_timestamp
from bugsink.pygments_extensions import guess_lexer_for_filename, lexer_for_platform
@@ -235,3 +239,31 @@ def format_var(value):
def incomplete(value):
# needed to disinguish between 'has an incomplete' attr (set by us) and 'contains an incomplete key' (event-data)
return hasattr(value, "incomplete")
def _date_with_milis_html(timestamp):
return mark_safe(
'<span class="whitespace-nowrap">' +
date(timestamp, "j M G:i:s") + "." +
'<span class="text-xs">' + date(timestamp, "u")[:3] + '</span></span>')
@register.filter
def timestamp_with_millis(value):
"""
Timestamp formatting with milliseconds; robust for datetime.datetime, as well as the strings/floats/ints that may
show up in the event data.
"""
if isinstance(value, datetime):
dt = value
else:
try:
dt = parse_timestamp(value)
except Exception:
return value
if dt is None:
return value
return _date_with_milis_html(dt)