1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

#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 * extras.ExportTemplate
* Renamed `content_type` field to `content_types` * Renamed `content_type` field to `content_types`
* extras.JobResult * extras.JobResult
* Added the `scheduled` field * Added `scheduled` and `started` datetime fields
* ipam.Aggregate * ipam.Aggregate
* Added a `comments` field * Added a `comments` field
* ipam.ASN * ipam.ASN

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -585,6 +585,10 @@ class JobResult(models.Model):
null=True, null=True,
blank=True blank=True
) )
started = models.DateTimeField(
null=True,
blank=True
)
completed = models.DateTimeField( completed = models.DateTimeField(
null=True, null=True,
blank=True blank=True
@ -639,9 +643,18 @@ class JobResult(models.Model):
return f"{int(minutes)} minutes, {seconds:.2f} seconds" 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): def set_status(self, status):
""" """
Helper method to change the status of the job result. If the target status is terminal, the completion Helper method to change the status of the job result. If the target status is terminal, the completion
time is also set. time is also set.
""" """
self.status = status self.status = status

View File

@ -83,6 +83,7 @@ def run_report(job_result, *args, **kwargs):
report = get_report(module_name, report_name) report = get_report(module_name, report_name)
try: try:
job_result.start()
report.run(job_result) report.run(job_result)
except Exception as e: except Exception as e:
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) 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): 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 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 = kwargs.pop('job_result')
job_result.start()
module, script_name = job_result.name.split('.', 1) module, script_name = job_result.name.split('.', 1)
script = get_script(module, script_name)() 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 = logging.getLogger(f"netbox.scripts.{module}.{script_name}")
logger.info(f"Running script (commit={commit})") logger.info(f"Running script (commit={commit})")

View File

@ -49,9 +49,11 @@ class JobResultTable(NetBoxTable):
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = JobResult model = JobResult
fields = ( 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): class CustomLinkTable(NetBoxTable):

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
{% block content-wrapper %} {% block content-wrapper %}
<div class="row p-3"> <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' %} {% include 'extras/htmx/report_result.html' %}
</div> </div>
</div> </div>

View File

@ -47,7 +47,7 @@
<div class="tab-content mb-3"> <div class="tab-content mb-3">
<div role="tabpanel" class="tab-pane active" id="log"> <div role="tabpanel" class="tab-pane active" id="log">
<div class="row"> <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' %} {% include 'extras/htmx/script_result.html' %}
</div> </div>
</div> </div>