Repository: apluslms/a-plus

Issue:
Automatic regrade: make "grader is unstable" - banner course specific
Currently, when there are enough many unfinished grading tasks in PendingSubmissions table, a globally visible "Grader is unstable" banner is shown in A+ on all pages. However, oftentimes the problem may concern a particular exercise and course, and the alert unnecessarily bothers teachers and students on other courses. The banner should therefore be made course-specific, and only show on courses that have more than GRADER_STABLE_THRESHOLD (current settings.py parameter) retries pending.


Discussion:
Additional idea: the banner could identify the exercise that causes problems. It seems that when problems start occurring, typically it has been specific exercise that raises this condition.

Resolving patch:
diff --git a/course/templatetags/base.py b/course/templatetags/base.py
--- a/course/templatetags/base.py
+++ b/course/templatetags/base.py
@@ -3,6 +3,7 @@
 from django import template
 from django.conf import settings
 from django.utils.safestring import mark_safe
+from django.utils.text import format_lazy
 from django.utils.translation import get_language, gettext_lazy as _
 from lib.helpers import remove_query_param_from_url, settings_text, update_url_params
 from exercise.submission_models import PendingSubmission
@@ -31,23 +32,35 @@ def get_date(cont, key):
 def brand_name():
     return mark_safe(settings.BRAND_NAME)
 
+
 @register.simple_tag
 def brand_name_long():
     return mark_safe(settings.BRAND_NAME_LONG)
 
+
 @register.simple_tag
 def brand_institution_name():
     return mark_safe(settings_text('BRAND_INSTITUTION_NAME'))
 
+
+@register.simple_tag
+def course_alert(instance):
+    exercises = PendingSubmission.objects.get_exercise_names_if_grader_is_unstable(instance)
+    if exercises:
+        message = format_lazy(
+            _('GRADER_PROBLEMS_ALERT -- {exercises}'),
+            exercises=exercises,
+        )
+        return mark_safe(format_lazy('<div class="alert alert-danger">{message}</div>', message=message))
+    return ''
+
+
 @register.simple_tag
 def site_alert():
     message = settings.SITEWIDE_ALERT_TEXT
     if message:
         return mark_safe('<div class="alert alert-danger">{}</div>'
                          .format(pick_localized(message)))
-    if not PendingSubmission.objects.is_grader_stable():
-        # Prefer configured alert text, if one is set
-        return mark_safe('<div class="alert alert-danger">{}</div>'.format(_('GRADER_PROBLEMS_ALERT')))
     return ''
 
 
diff --git a/exercise/submission_models.py b/exercise/submission_models.py
--- a/exercise/submission_models.py
+++ b/exercise/submission_models.py
@@ -829,6 +829,24 @@ def is_grader_stable(self):
         return not (total_retries and total_retries > settings.GRADER_STABLE_THRESHOLD)
 
 
+    def get_exercise_names_if_grader_is_unstable(self, instance):
+        total_retries_per_exercise = self.values(
+            'submission__exercise__name',
+        ).filter(
+            submission__exercise__course_module__course_instance=instance.id,
+        ).annotate(
+            num_retries=models.Sum('num_retries'),
+        ).order_by(
+            '-num_retries',
+        )[:10]
+        total_retries = sum(entry['num_retries'] for entry in total_retries_per_exercise)
+        # Check if the grader can be considered unstable on this course instance
+        if total_retries > settings.GRADER_STABLE_THRESHOLD:
+            exercises = ", ".join(f"'{entry['submission__exercise__name']}'" for entry in total_retries_per_exercise)
+            return exercises
+        return ''
+
+
 class PendingSubmission(models.Model):
     submission = models.OneToOneField(Submission,
         verbose_name=_('LABEL_SUBMISSION'),


Test patch: