Skip to content

Commit

Permalink
Add upgrade to remediate end_timestamps for mastery logs.
Browse files Browse the repository at this point in the history
  • Loading branch information
rtibbles committed Nov 22, 2024
1 parent e78979b commit 7925bfc
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
109 changes: 109 additions & 0 deletions kolibri/core/logger/test/test_upgrades.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from uuid import uuid4

from django.test import TestCase
from django.utils import timezone

from kolibri.core.auth.models import Facility
from kolibri.core.auth.models import FacilityUser
from kolibri.core.logger.models import AttemptLog
from kolibri.core.logger.models import ContentSessionLog
from kolibri.core.logger.models import ContentSummaryLog
from kolibri.core.logger.models import MasteryLog
from kolibri.core.logger.upgrade import fix_masterylog_end_timestamps


class MasteryLogEndTimestampUpgradeTest(TestCase):
def setUp(self):
self.facility = Facility.objects.create()
self.user = FacilityUser.objects.create(
username="learner", facility=self.facility
)
now = timezone.now()

# Create base content summary log
self.summary_log = ContentSummaryLog.objects.create(
user=self.user,
content_id=uuid4().hex,
channel_id=uuid4().hex,
kind="exercise",
start_timestamp=now,
end_timestamp=now + timezone.timedelta(minutes=10),
)

# Case 1: MasteryLog with attempts
self.attempt_session = ContentSessionLog.objects.create(
user=self.user,
content_id=self.summary_log.content_id,
channel_id=self.summary_log.channel_id,
kind="exercise",
start_timestamp=now,
end_timestamp=now + timezone.timedelta(minutes=3),
)

self.attempt_mastery = MasteryLog.objects.create(
user=self.user,
summarylog=self.summary_log,
mastery_level=2,
start_timestamp=now,
end_timestamp=now,
)

AttemptLog.objects.create(
masterylog=self.attempt_mastery,
sessionlog=self.attempt_session,
start_timestamp=now,
end_timestamp=now - timezone.timedelta(minutes=3),
complete=True,
correct=1,
)

AttemptLog.objects.create(
masterylog=self.attempt_mastery,
sessionlog=self.attempt_session,
start_timestamp=now,
end_timestamp=now - timezone.timedelta(minutes=2),
complete=True,
correct=1,
)

self.last_attempt = AttemptLog.objects.create(
masterylog=self.attempt_mastery,
sessionlog=self.attempt_session,
start_timestamp=now,
end_timestamp=now + timezone.timedelta(minutes=3),
complete=True,
correct=1,
)

# Case 2: MasteryLog with only summary log
self.summary_session = ContentSessionLog.objects.create(
user=self.user,
content_id=self.summary_log.content_id,
channel_id=self.summary_log.channel_id,
kind="exercise",
start_timestamp=now,
end_timestamp=now,
)
self.summary_only_mastery = MasteryLog.objects.create(
user=self.user,
summarylog=self.summary_log,
mastery_level=3,
start_timestamp=now,
end_timestamp=now,
)

fix_masterylog_end_timestamps()

def test_attempt_logs_case(self):
"""Test MasteryLog with attempt logs gets end_timestamp from last attempt"""
self.attempt_mastery.refresh_from_db()
self.assertEqual(
self.attempt_mastery.end_timestamp, self.last_attempt.end_timestamp
)

def test_summary_log_case(self):
"""Test MasteryLog with only summary log gets end_timestamp from summary"""
self.summary_only_mastery.refresh_from_db()
self.assertEqual(
self.summary_only_mastery.end_timestamp, self.summary_log.end_timestamp
)
35 changes: 35 additions & 0 deletions kolibri/core/logger/upgrade.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
"""
A file to contain specific logic to handle version upgrades in Kolibri.
"""
from django.db.models import F
from django.db.models import Max
from django.db.models import OuterRef
from django.db.models import Subquery

from kolibri.core.logger.models import AttemptLog
from kolibri.core.logger.models import ContentSummaryLog
from kolibri.core.logger.models import ExamLog
from kolibri.core.logger.models import MasteryLog
from kolibri.core.logger.utils.attempt_log_consolidation import (
consolidate_quiz_attempt_logs,
)
Expand Down Expand Up @@ -57,3 +63,32 @@ def fix_duplicated_attempt_logs():
item and non-null masterylog_id.
"""
consolidate_quiz_attempt_logs(AttemptLog.objects.all())


@version_upgrade(old_version=">0.15.0,<0.18.0")
def fix_masterylog_end_timestamps():
"""
Fix any MasteryLogs that have an end_timestamp that was not updated after creation due to a bug in the
integrated logging API endpoint.
"""
# Fix the MasteryLogs that that have attempts - infer from the end_timestamp of the last attempt.
attempt_subquery = (
AttemptLog.objects.filter(masterylog=OuterRef("pk"))
.values("masterylog")
.annotate(max_end=Max("end_timestamp"))
.values("max_end")
)

MasteryLog.objects.filter(
end_timestamp=F("start_timestamp"), attemptlogs__isnull=False
).update(end_timestamp=Subquery(attempt_subquery))
# Fix the MasteryLogs that don't have any attempts - just set the end_timestamp to the end_timestamp of the summary log.
summary_subquery = ContentSummaryLog.objects.filter(
masterylogs=OuterRef("pk")
).values("end_timestamp")

MasteryLog.objects.filter(
end_timestamp=F("start_timestamp"),
completion_timestamp__isnull=True,
attemptlogs__isnull=True,
).update(end_timestamp=Subquery(summary_subquery))

0 comments on commit 7925bfc

Please sign in to comment.