Display the duration for running scripts
This commit is contained in:
parent
736fd38e08
commit
cd7fdf2267
|
|
@ -11,9 +11,13 @@
|
|||
{% else %}
|
||||
{% trans "Created" %}: <strong>{{ job.created|isodatetime }}</strong>
|
||||
{% endif %}
|
||||
{% if job.completed %}
|
||||
{% trans "Duration" %}: <strong>{{ job.duration }}</strong>
|
||||
{% endif %}
|
||||
{# For a running job this reflects the time elapsed so far; the container refreshes every 5s #}
|
||||
{% with execution_time=job.elapsed_time|humanize_duration %}
|
||||
{% if execution_time %}
|
||||
{% trans "Execution time" %}:
|
||||
<strong{% if not job.completed %} class="text-primary" title="{% trans "Still running" %}"{% endif %}>{{ execution_time }}</strong>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
|
||||
</p>
|
||||
{% if job.completed %}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,25 @@ __all__ = (
|
|||
|
||||
def humanize_duration(value):
|
||||
"""
|
||||
Express a timedelta in a human-friendly format. Example: 1h 5m 23s. Returns an empty string
|
||||
for None; zero-duration timedeltas render as "0s".
|
||||
Express a timedelta in a human-friendly format. Example: 1h 5m 23s. Sub-second durations are
|
||||
rendered with millisecond precision (e.g. 0.43s). Returns an empty string for None; zero and
|
||||
negative durations render as "0s".
|
||||
"""
|
||||
if value is None:
|
||||
return ''
|
||||
|
||||
# Negative durations (which can result from clock skew) are clamped to zero
|
||||
total_seconds = max(value.total_seconds(), 0)
|
||||
|
||||
# Render sub-second durations with millisecond precision, as rounding them to whole seconds
|
||||
# would report every short-lived duration as zero. Trailing zeros are stripped.
|
||||
if 0 < total_seconds < 1:
|
||||
milliseconds = f'{total_seconds:.3f}'.rstrip('0').rstrip('.')
|
||||
if milliseconds != '0':
|
||||
return f'{milliseconds}s'
|
||||
|
||||
# Round to whole seconds and decompose
|
||||
total_seconds = int(value.total_seconds())
|
||||
days, remainder = divmod(total_seconds, 86400)
|
||||
days, remainder = divmod(int(total_seconds), 86400)
|
||||
hours, remainder = divmod(remainder, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from netbox.ui.attrs import (
|
|||
)
|
||||
from utilities.forms import TableConfigForm, get_selected_values
|
||||
from utilities.forms.mixins import FORM_FIELD_LOOKUPS
|
||||
from utilities.string import humanize_duration
|
||||
from utilities.views import get_action_url, get_viewname
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -31,6 +32,7 @@ __all__ = (
|
|||
'get_item',
|
||||
'get_key',
|
||||
'humanize_disk_capacity',
|
||||
'humanize_duration_filter',
|
||||
'humanize_ram_capacity',
|
||||
'humanize_speed',
|
||||
'icon_from_status',
|
||||
|
|
@ -211,6 +213,21 @@ def _format_speed(speed, divisor, unit):
|
|||
return f'{whole}.{fraction} {unit}'
|
||||
|
||||
|
||||
@register.filter('humanize_duration')
|
||||
def humanize_duration_filter(value):
|
||||
"""
|
||||
Express a timedelta in a human-friendly format, always using the largest appropriate units.
|
||||
Sub-second durations are rendered with millisecond precision.
|
||||
|
||||
Examples:
|
||||
|
||||
timedelta(seconds=90) => "1m 30s"
|
||||
timedelta(hours=1, minutes=5, seconds=23) => "1h 5m 23s"
|
||||
timedelta(milliseconds=430) => "0.43s"
|
||||
"""
|
||||
return humanize_duration(value)
|
||||
|
||||
|
||||
@register.filter()
|
||||
def humanize_speed(speed):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -28,6 +28,20 @@ class HumanizeDurationTest(TestCase):
|
|||
def test_whole_minute_omits_seconds(self):
|
||||
self.assertEqual(humanize_duration(timedelta(minutes=2)), '2m')
|
||||
|
||||
def test_sub_second_rounds_down_to_zero(self):
|
||||
# Fractional seconds are truncated; a sub-second duration reads as 0s.
|
||||
self.assertEqual(humanize_duration(timedelta(milliseconds=500)), '0s')
|
||||
def test_sub_second_renders_decimal(self):
|
||||
# Sub-second durations retain millisecond precision, with trailing zeros stripped.
|
||||
self.assertEqual(humanize_duration(timedelta(milliseconds=500)), '0.5s')
|
||||
self.assertEqual(humanize_duration(timedelta(milliseconds=430)), '0.43s')
|
||||
self.assertEqual(humanize_duration(timedelta(milliseconds=4)), '0.004s')
|
||||
|
||||
def test_sub_millisecond_rounds_to_zero(self):
|
||||
# Anything below a millisecond has no decimal representation, so it reads as 0s.
|
||||
self.assertEqual(humanize_duration(timedelta(microseconds=400)), '0s')
|
||||
|
||||
def test_fractional_seconds_truncated_above_one_second(self):
|
||||
self.assertEqual(humanize_duration(timedelta(seconds=1, milliseconds=999)), '1s')
|
||||
|
||||
def test_negative_duration_clamped_to_zero(self):
|
||||
# A negative duration (e.g. resulting from clock skew) never renders as negative.
|
||||
self.assertEqual(humanize_duration(timedelta(seconds=-1.5)), '0s')
|
||||
self.assertEqual(humanize_duration(timedelta(days=-2)), '0s')
|
||||
|
|
|
|||
Loading…
Reference in New Issue