Repository: mozilla/telemetry-analysis-service

Issue:
Add a status indicator that shows if a scheduled job is currently running



Discussion:
This is somewhat related to #271 which will show the job runs in a new tab of the job detail page and the "latest run" of a job in the dashboard. I can amend that feature to also show the current state of the "latest run".
My plan here is to simply add a new column to the jobs list. For those with the added permission they'd get a good view of all jobs. The value of that column could either be text based of the latest run's status, possibly color coded (green for running, red for terminated with error, etc).

Resolving patch:
diff --git a/atmo/jobs/models.py b/atmo/jobs/models.py
--- a/atmo/jobs/models.py
+++ b/atmo/jobs/models.py
@@ -237,6 +237,11 @@ def should_run(self):
     def is_public(self):
         return self.result_visibility == self.RESULT_PUBLIC
 
+    @property
+    def is_active(self):
+        return (self.latest_run and
+                self.latest_run.status in Cluster.ACTIVE_STATUS_LIST)
+
     @property
     def notebook_name(self):
         return self.notebook_s3_key.rsplit('/', 1)[-1]
diff --git a/atmo/jobs/templatetags/notebook.py b/atmo/jobs/templatetags/notebook.py
--- a/atmo/jobs/templatetags/notebook.py
+++ b/atmo/jobs/templatetags/notebook.py
@@ -4,6 +4,7 @@
 from django import template
 from django.template.defaultfilters import stringfilter
 
+
 register = template.Library()
 
 
diff --git a/atmo/jobs/templatetags/status.py b/atmo/jobs/templatetags/status.py
new file mode 100644
--- /dev/null
+++ b/atmo/jobs/templatetags/status.py
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+from django import template
+from django.template.defaultfilters import stringfilter
+
+from atmo.clusters.models import Cluster
+
+
+register = template.Library()
+
+
+@register.filter
+@stringfilter
+def status_icon(status):
+    if status in Cluster.ACTIVE_STATUS_LIST:
+        return 'glyphicon-play'
+    elif status in Cluster.TERMINATED_STATUS_LIST:
+        return 'glyphicon-stop'
+    elif status in Cluster.FAILED_STATUS_LIST:
+        return 'glyphicon-exclamation-sign'
+
+
+@register.filter
+@stringfilter
+def status_color(status):
+    if status in Cluster.ACTIVE_STATUS_LIST:
+        return 'status-running'
+    elif status in Cluster.FAILED_STATUS_LIST:
+        return 'status-errors'


Test patch:
diff --git a/tests/jobs/test_models.py b/tests/jobs/test_models.py
--- a/tests/jobs/test_models.py
+++ b/tests/jobs/test_models.py
@@ -119,6 +119,7 @@ def test_update_status_running(request, mocker,
         },
     )
     spark_job.latest_run.update_status()
+    assert spark_job.is_active
     assert spark_job.latest_run.status == Cluster.STATUS_RUNNING
     assert spark_job.latest_run.scheduled_date == one_hour_ago
     assert spark_job.latest_run.run_date == now
@@ -245,6 +246,7 @@ def test_has_timed_out_never_ran(has_timed_out_factory):
     spark_job, timeout_delta = has_timed_out_factory()
     spark_job.latest_run.scheduled_date = None
     spark_job.latest_run.status = models.DEFAULT_STATUS
+    assert not spark_job.is_active
     assert spark_job.is_runnable
     assert spark_job.has_never_run
     assert not spark_job.has_timed_out
@@ -265,6 +267,7 @@ def test_has_timed_out_ran_and_terminated(has_timed_out_factory):
     spark_job, timeout_delta = has_timed_out_factory()
     spark_job.latest_run.scheduled_date = spark_job.start_date + timedelta(minutes=10)
     spark_job.latest_run.status = Cluster.STATUS_TERMINATED
+    assert not spark_job.is_active
     assert spark_job.is_runnable
     assert not spark_job.has_never_run
     assert not spark_job.has_timed_out
diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py
--- a/tests/test_templatetags.py
+++ b/tests/test_templatetags.py
@@ -1,8 +1,10 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, you can obtain one at http://mozilla.org/MPL/2.0/.
+from atmo.clusters.models import Cluster
 from atmo.jobs.templatetags.notebook import is_notebook
-from atmo.templatetags import url_update, full_url
+from atmo.jobs.templatetags.status import status_color, status_icon
+from atmo.templatetags import full_url, url_update
 
 
 def test_is_notebook():
@@ -19,3 +21,21 @@ def test_url_update():
 
 def test_get_full_url():
     assert full_url('/test/') == 'http://localhost:8000/test/'
+
+
+def test_status_icon():
+    for status in Cluster.ACTIVE_STATUS_LIST:
+        assert status_icon(status) == 'glyphicon-play'
+    for status in Cluster.TERMINATED_STATUS_LIST:
+        assert status_icon(status) == 'glyphicon-stop'
+    for status in Cluster.FAILED_STATUS_LIST:
+        assert status_icon(status) == 'glyphicon-exclamation-sign'
+
+
+def test_status_color():
+    for status in Cluster.ACTIVE_STATUS_LIST:
+        assert status_color(status) == 'status-running'
+    for status in Cluster.TERMINATED_STATUS_LIST:
+        assert status_color(status) is None
+    for status in Cluster.FAILED_STATUS_LIST:
+        assert status_color(status) == 'status-errors'