#8366: Add started field to JobResult

This commit is contained in:
jeremystretch 2022-11-15 14:38:58 -05:00
parent 87727c71f7
commit 0bcc59a1e9
15 changed files with 84 additions and 45 deletions

View File

@ -131,7 +131,7 @@ This release introduces a new programmatic API that enables plugins and custom s
* extras.ExportTemplate
* Renamed `content_type` field to `content_types`
* extras.JobResult
* Added the `scheduled` field
* Added `scheduled` and `started` datetime fields
* ipam.Aggregate
* Added a `comments` field
* ipam.ASN

View File

@ -385,8 +385,8 @@ class JobResultSerializer(BaseModelSerializer):
class Meta:
model = JobResult
fields = [
'id', 'url', 'display', 'status', 'created', 'scheduled', 'completed', 'name', 'obj_type', 'user', 'data',
'job_id',
'id', 'url', 'display', 'status', 'created', 'scheduled', 'started', 'completed', 'name', 'obj_type',
'user', 'data', 'job_id',
]

View File

@ -503,15 +503,6 @@ class JobResultFilterSet(BaseFilterSet):
field_name='created',
lookup_expr='gte'
)
completed = django_filters.DateTimeFilter()
completed__before = django_filters.DateTimeFilter(
field_name='completed',
lookup_expr='lte'
)
completed__after = django_filters.DateTimeFilter(
field_name='completed',
lookup_expr='gte'
)
scheduled = django_filters.DateTimeFilter()
scheduled__before = django_filters.DateTimeFilter(
field_name='scheduled',
@ -521,6 +512,24 @@ class JobResultFilterSet(BaseFilterSet):
field_name='scheduled',
lookup_expr='gte'
)
started = django_filters.DateTimeFilter()
started__before = django_filters.DateTimeFilter(
field_name='started',
lookup_expr='lte'
)
started__after = django_filters.DateTimeFilter(
field_name='started',
lookup_expr='gte'
)
completed = django_filters.DateTimeFilter()
completed__before = django_filters.DateTimeFilter(
field_name='completed',
lookup_expr='lte'
)
completed__after = django_filters.DateTimeFilter(
field_name='completed',
lookup_expr='gte'
)
status = django_filters.MultipleChoiceFilter(
choices=JobResultStatusChoices,
null_value=None
@ -528,9 +537,7 @@ class JobResultFilterSet(BaseFilterSet):
class Meta:
model = JobResult
fields = [
'id', 'status', 'created', 'scheduled', 'completed', 'user', 'obj_type', 'name'
]
fields = ('id', 'status', 'user', 'obj_type', 'name')
def search(self, queryset, name, value):
if not value.strip():

View File

@ -73,11 +73,10 @@ class JobResultFilterForm(SavedFiltersMixin, FilterForm):
(None, ('q', 'filter_id')),
('Attributes', ('obj_type', 'status')),
('Creation', (
'created__before', 'created__after', 'completed__before', 'completed__after', 'scheduled__before',
'scheduled__after', 'user',
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
'started__after', 'completed__before', 'completed__after', 'user',
)),
)
obj_type = ContentTypeChoiceField(
label=_('Object Type'),
queryset=ContentType.objects.all(),
@ -96,14 +95,6 @@ class JobResultFilterForm(SavedFiltersMixin, FilterForm):
required=False,
widget=DateTimePicker()
)
completed__after = forms.DateTimeField(
required=False,
widget=DateTimePicker()
)
completed__before = forms.DateTimeField(
required=False,
widget=DateTimePicker()
)
scheduled__after = forms.DateTimeField(
required=False,
widget=DateTimePicker()
@ -112,6 +103,22 @@ class JobResultFilterForm(SavedFiltersMixin, FilterForm):
required=False,
widget=DateTimePicker()
)
started__after = forms.DateTimeField(
required=False,
widget=DateTimePicker()
)
started__before = forms.DateTimeField(
required=False,
widget=DateTimePicker()
)
completed__after = forms.DateTimeField(
required=False,
widget=DateTimePicker()
)
completed__before = forms.DateTimeField(
required=False,
widget=DateTimePicker()
)
user = DynamicModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,

View File

@ -13,6 +13,11 @@ class Migration(migrations.Migration):
name='scheduled',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='jobresult',
name='started',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterModelOptions(
name='jobresult',
options={'ordering': ['-created']},

View File

@ -12,7 +12,7 @@ class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('extras', '0079_jobresult_scheduled'),
('extras', '0079_scheduled_jobs'),
]
operations = [

View File

@ -585,6 +585,10 @@ class JobResult(models.Model):
null=True,
blank=True
)
started = models.DateTimeField(
null=True,
blank=True
)
completed = models.DateTimeField(
null=True,
blank=True
@ -639,6 +643,15 @@ class JobResult(models.Model):
return f"{int(minutes)} minutes, {seconds:.2f} seconds"
def start(self):
"""
Record the job's start time and update its status to "running."
"""
if self.started is None:
self.started = timezone.now()
self.status = JobResultStatusChoices.STATUS_RUNNING
JobResult.objects.filter(pk=self.pk).update(started=self.started, status=self.status)
def set_status(self, status):
"""
Helper method to change the status of the job result. If the target status is terminal, the completion

View File

@ -83,6 +83,7 @@ def run_report(job_result, *args, **kwargs):
report = get_report(module_name, report_name)
try:
job_result.start()
report.run(job_result)
except Exception as e:
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)

View File

@ -433,16 +433,14 @@ def is_variable(obj):
def run_script(data, request, commit=True, *args, **kwargs):
"""
A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
exists outside of the Script class to ensure it cannot be overridden by a script author.
exists outside the Script class to ensure it cannot be overridden by a script author.
"""
job_result = kwargs.pop('job_result')
job_result.start()
module, script_name = job_result.name.split('.', 1)
script = get_script(module, script_name)()
job_result.status = JobResultStatusChoices.STATUS_RUNNING
job_result.save()
logger = logging.getLogger(f"netbox.scripts.{module}.{script_name}")
logger.info(f"Running script (commit={commit})")

View File

@ -49,9 +49,11 @@ class JobResultTable(NetBoxTable):
class Meta(NetBoxTable.Meta):
model = JobResult
fields = (
'pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'completed', 'user', 'job_id',
'pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'started', 'completed', 'user', 'job_id',
)
default_columns = (
'pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'started', 'completed', 'user',
)
default_columns = ('pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'completed', 'user',)
class CustomLinkTable(NetBoxTable):

View File

@ -726,7 +726,7 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
'report': report,
'result': result,
})
if result.completed:
if result.completed or not result.started:
response.status_code = 286
return response
@ -860,7 +860,7 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, GetScriptMixin, View)
'script': script,
'result': result,
})
if result.completed:
if result.completed or not result.started:
response.status_code = 286
return response

View File

@ -1,9 +1,12 @@
{% load helpers %}
<p>
Initiated: <strong>{{ result.created|annotated_date }}</strong>
{% if result.scheduled %}
{% if result.started %}
Started: <strong>{{ result.started|annotated_date }}</strong>
{% elif result.scheduled %}
Scheduled for: <strong>{{ result.scheduled|annotated_date }}</strong>
{% else %}
Created: <strong>{{ result.created|annotated_date }}</strong>
{% endif %}
{% if result.completed %}
Duration: <strong>{{ result.duration }}</strong>
@ -71,6 +74,6 @@
</table>
</div>
</div>
{% else %}
{% elif result.started %}
{% include 'extras/inc/result_pending.html' %}
{% endif %}

View File

@ -2,9 +2,12 @@
{% load log_levels %}
<p>
Initiated: <strong>{{ result.created|annotated_date }}</strong>
{% if result.scheduled %}
{% if result.started %}
Started: <strong>{{ result.started|annotated_date }}</strong>
{% elif result.scheduled %}
Scheduled for: <strong>{{ result.scheduled|annotated_date }}</strong>
{% else %}
Created: <strong>{{ result.created|annotated_date }}</strong>
{% endif %}
{% if result.completed %}
Duration: <strong>{{ result.duration }}</strong>
@ -48,6 +51,6 @@
{% else %}
<p class="text-muted">None</p>
{% endif %}
{% else %}
{% elif result.started %}
{% include 'extras/inc/result_pending.html' %}
{% endif %}

View File

@ -4,7 +4,7 @@
{% block content-wrapper %}
<div class="row p-3">
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:report_result' job_result_pk=result.pk %}" hx-trigger="every 3s"{% endif %}>
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:report_result' job_result_pk=result.pk %}" hx-trigger="every 5s"{% endif %}>
{% include 'extras/htmx/report_result.html' %}
</div>
</div>

View File

@ -47,7 +47,7 @@
<div class="tab-content mb-3">
<div role="tabpanel" class="tab-pane active" id="log">
<div class="row">
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:script_result' job_result_pk=result.pk %}" hx-trigger="every 3s"{% endif %}>
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:script_result' job_result_pk=result.pk %}" hx-trigger="every 5s"{% endif %}>
{% include 'extras/htmx/script_result.html' %}
</div>
</div>