Repository: learningequality/kolibri

Issue:
Unable to submit surveys
## Observed behavior
When testing surveys, @radinamatic noticed that there are significant errors in the console during the interaction of the survey, but most critically, that the survey does not submit at the end. I was able to reproduce this and I think it's related to the changes of the `finish` event in https://github.com/learningequality/kolibri/pull/10747/

## Errors and logs

<img width="1434" alt="Screenshot 2023-06-16 at 9 16 38 AM" src="https://github.com/learningequality/kolibri/assets/17235236/e5636f3d-d26c-469b-bfd7-8963d0e590e2">

## Expected behavior
User should be able to submit surveys, even if they are a feature currently only used in certain scenarios.

## Steps to reproduce
Choose a survey from the QA channel. Complete it. Note the errors in the console while filling out your answers. When you get to the "submit" step, note that the survey does not submit but stays on the screen with the modal open. 

## Context
latest develop


Discussion:
Just noting that the survey does get submitted at some point, it's just not communicated to the front-end correctly: if page is reloaded at that point (loader still spinning behind the modal), user will see the survey report.
I assume the same is true for practice quizzes?
Yep, apparently it does...


https://github.com/learningequality/kolibri/assets/1457929/6707386f-8309-40d1-9090-454a89928590



Resolving patch:
diff --git a/kolibri/core/logger/api.py b/kolibri/core/logger/api.py
--- a/kolibri/core/logger/api.py
+++ b/kolibri/core/logger/api.py
@@ -6,10 +6,12 @@
 
 from django.core.exceptions import PermissionDenied
 from django.db import transaction
+from django.db.models import Case
 from django.db.models import IntegerField
 from django.db.models import Q
 from django.db.models import Sum
 from django.db.models import Value
+from django.db.models import When
 from django.db.models.functions import Coalesce
 from django.http import Http404
 from django_filters.rest_framework import BooleanFilter
@@ -892,9 +894,25 @@ def retrieve(self, request, pk=None):
         if request.user.is_anonymous or pk != request.user.id:
             raise PermissionDenied("Can only access progress data for self")
         progress = (
-            request.user.contentsummarylog_set.filter(progress=1)
-            .aggregate(Sum("progress"))
+            request.user.contentsummarylog_set.annotate(
+                mastery_progress=Sum(
+                    Case(
+                        When(masterylogs__complete=True, then=Value(1)),
+                        When(masterylogs__complete=False, then=Value(0)),
+                        default=Value(None),
+                        output_field=IntegerField(),
+                    )
+                )
+            )
+            .filter(Q(progress=1) | Q(mastery_progress__isnull=False))
+            .aggregate(
+                progress__sum=Sum(
+                    Coalesce("mastery_progress", "progress"),
+                    output_field=IntegerField(),
+                )
+            )
             .get("progress__sum")
+            or 0
         )
         return Response(
             {


Test patch:
diff --git a/kolibri/core/logger/test/test_api.py b/kolibri/core/logger/test/test_api.py
--- a/kolibri/core/logger/test/test_api.py
+++ b/kolibri/core/logger/test/test_api.py
@@ -19,6 +19,7 @@
 from ..models import ContentSessionLog
 from ..models import ContentSummaryLog
 from ..models import GenerateCSVLogRequest
+from ..models import MasteryLog
 from .factory_logger import ContentSessionLogFactory
 from .factory_logger import ContentSummaryLogFactory
 from .factory_logger import FacilityUserFactory
@@ -523,3 +524,126 @@ def test_diff_no_attempts_second_try(self):
         )
         diff = response.data.get("diff")
         self.assertEqual(diff["correct"], 3.0)
+
+
+class TotalContentProgressViewSetTest(APITestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.facility = FacilityFactory.create()
+        # provision device to pass the setup_wizard middleware check
+        provision_device()
+        cls.user1 = FacilityUserFactory.create(facility=cls.facility)
+        cls.summary_logs = [
+            ContentSummaryLogFactory.create(
+                user=cls.user1,
+                content_id=uuid.uuid4().hex,
+                channel_id="6199dde695db4ee4ab392222d5af1e5c",
+            )
+            for _ in range(3)
+        ]
+
+    def test_summary_logs_only_no_complete(self):
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 0)
+
+    def test_summary_logs_only_some_complete(self):
+        log = self.summary_logs[0]
+        log.progress = 1.0
+        log.save()
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 1)
+
+    def test_summary_logs_all_some_complete(self):
+        ContentSummaryLog.objects.all().update(progress=1.0)
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 3)
+
+    def test_mastery_logs_no_complete(self):
+        MasteryLog.objects.create(
+            summarylog=self.summary_logs[0],
+            mastery_level=1,
+            user=self.user1,
+            start_timestamp=local_now(),
+        )
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 0)
+
+    def test_mastery_logs_no_complete_conflict_with_summarylog(self):
+        MasteryLog.objects.create(
+            summarylog=self.summary_logs[0],
+            mastery_level=1,
+            user=self.user1,
+            start_timestamp=local_now(),
+        )
+        self.summary_logs[0].complete = True
+        self.summary_logs[0].save()
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 0)
+
+    def test_mastery_log_complete_conflict_with_summarylog(self):
+        MasteryLog.objects.create(
+            summarylog=self.summary_logs[0],
+            mastery_level=1,
+            user=self.user1,
+            start_timestamp=local_now(),
+            complete=True,
+        )
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 1)
+
+    def test_mastery_logs_all_summarylog_complete_one_masterylog_complete(self):
+        MasteryLog.objects.create(
+            summarylog=self.summary_logs[0],
+            mastery_level=1,
+            user=self.user1,
+            start_timestamp=local_now(),
+            complete=True,
+        )
+        ContentSummaryLog.objects.all().update(progress=1.0)
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 3)
+
+    def test_mastery_logs_all_summarylog_complete_three_masterylog_complete_one_incomplete(
+        self,
+    ):
+        MasteryLog.objects.create(
+            summarylog=self.summary_logs[0],
+            mastery_level=1,
+            user=self.user1,
+            start_timestamp=local_now(),
+        )
+        for i in range(2, 5):
+            MasteryLog.objects.create(
+                summarylog=self.summary_logs[0],
+                mastery_level=i,
+                user=self.user1,
+                start_timestamp=local_now(),
+                complete=True,
+            )
+        ContentSummaryLog.objects.all().update(progress=1.0)
+        self.client.force_login(self.user1)
+        response = self.client.get(
+            reverse("kolibri:core:userprogress-detail", kwargs={"pk": self.user1.id}),
+        )
+        self.assertEqual(response.data["progress"], 5)