Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion django_email_learning/jobs/deliver_contents_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ def get_delivery_queue(self) -> DeliveryQueueProtocol:
settings, "DJANGO_EMAIL_LEARNING", {}
)
try:
return import_string(DJANGO_EMAIL_LEARNING_SETTINGS["DELIVERY_QUEUE"])
configured_delivery_queue = import_string(
DJANGO_EMAIL_LEARNING_SETTINGS["DELIVERY_QUEUE"]
)
return (
configured_delivery_queue()
if isinstance(configured_delivery_queue, type)
else configured_delivery_queue
)
except KeyError:
from django_email_learning.services.defaults.database_delivery_queue import (
DatabaseDeliveryQueue,
Expand Down
13 changes: 13 additions & 0 deletions django_email_learning/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
MinLengthValidator,
)
from django_email_learning.services.email_sender_service import EmailSenderService
from django_email_learning.services.metrics_service import MetricsService
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.core.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -60,6 +61,9 @@ class DeliveryStatus(StrEnum):
BLOCKED = "blocked"


METRIC_SERVICE = MetricsService()


def is_domain_or_ip(value: str) -> None:
"""
Validate if the given value is a valid domain name or IP address.
Expand Down Expand Up @@ -557,6 +561,10 @@ def graduate(self) -> None:
)
self.status = EnrollmentStatus.COMPLETED
self.final_state_at = timezone.now()
METRIC_SERVICE.user_completed_course(
course_slug=self.course.slug,
organization_id=self.course.organization.id,
)
logger.info(
f"Learner ID {self.learner.id} has completed the course {self.course.title}."
)
Expand Down Expand Up @@ -609,6 +617,11 @@ def fail(self) -> None:
self.status = EnrollmentStatus.DEACTIVATED
self.deactivation_reason = DeactivationReason.FAILED
self.final_state_at = timezone.now()
METRIC_SERVICE.user_enrollment_deactivated(
course_slug=self.course.slug,
organization_id=self.course.organization.id,
reason=DeactivationReason.FAILED,
)
logger.info(
f"Learner ID {self.learner.id} has failed the course {self.course.title}."
)
Expand Down
9 changes: 9 additions & 0 deletions django_email_learning/personalised/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
QuizSubmissionRequest,
QuestionResponse,
)
from django_email_learning.services.metrics_service import MetricsService
from django_email_learning.services import jwt_service
from django.utils.translation import gettext as _
from django_email_learning.models import (
Expand All @@ -19,6 +20,8 @@
import json
import logging

METRIC_SERVICE = MetricsService()

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -113,6 +116,12 @@ def post(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
)
delivery.repeat_delivery_in_days(1)

METRIC_SERVICE.quiz_submitted(
course_slug=enrolment.course.slug,
organization_id=enrolment.course.organization.id,
quiz_id=quiz.id,
is_passed=passed,
)
return JsonResponse(
{
"score": score,
Expand Down
34 changes: 34 additions & 0 deletions django_email_learning/ports/metric_recorder_protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Protocol
from django_email_learning.models import DeactivationReason


class MetricRecorderProtocol(Protocol):
def user_enrolled_in_course(self, course_slug: str, organization_id: int) -> None:
...

def quiz_sent(self, course_slug: str, organization_id: int, quiz_id: int) -> None:
...

def lesson_sent(
self, course_slug: str, organization_id: int, lesson_id: int
) -> None:
...

def user_enrollment_activated(self, course_slug: str, organization_id: int) -> None:
...

def user_enrollment_deactivated(
self, course_slug: str, organization_id: int, reason: DeactivationReason
) -> None:
...

def user_completed_course(self, course_slug: str, organization_id: int) -> None:
...

def quiz_submitted(
self, course_slug: str, organization_id: int, quiz_id: int, is_passed: bool
) -> None:
...

def method_executed(self, method_name: str, execution_time: int) -> None:
...
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from django_email_learning.services.utils import mask_email
from django_email_learning.services.email_sender_service import EmailSenderService
from django_email_learning.services import jwt_service
from django_email_learning.services.metrics_service import MetricsService
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
Expand All @@ -36,6 +37,7 @@ class EnrollCommand(AbstractCommand):
no_verification: bool = False

def execute(self) -> None:
metric_service = MetricsService()
# Check if the email is blocked
if BlockedEmail.objects.filter(email=self.email).exists():
self.logger.info(
Expand Down Expand Up @@ -88,6 +90,8 @@ def execute(self) -> None:
f"Enrollment Successful: Learner ID {learner.id} enrolled in course '{self.course_slug}'. Enrollment ID: {enrollment.id}"
)

metric_service.user_enrolled_in_course(self.course_slug, self.organization_id)

if self.no_verification:
self.logger.info(
f"Verification email skipped for Enrollment ID: {enrollment.id} as per command parameter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
)
from django_email_learning.models import Lesson, CourseContent
from django_email_learning.services.email_sender_service import EmailSenderService
from django_email_learning.services.metrics_service import MetricsService
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from typing import Literal
Expand All @@ -18,6 +19,7 @@ class SendLessonCommand(AbstractCommand):
email: str

def execute(self) -> None:
metric_service = MetricsService()
content = CourseContent.objects.get(id=self.content_id)
if not content.lesson:
raise LessonNotFoundError(
Expand Down Expand Up @@ -52,3 +54,8 @@ def execute(self) -> None:
)

email_service.send(email_message)
metric_service.lesson_sent(
course_slug=content.course.slug,
organization_id=content.course.organization.id,
lesson_id=lesson.id,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
)
from django_email_learning.models import Quiz, CourseContent
from django_email_learning.services.email_sender_service import EmailSenderService
from django_email_learning.services.metrics_service import MetricsService
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from typing import Literal
Expand All @@ -19,6 +20,7 @@ class SendQuizCommand(AbstractCommand):
content_id: int

def execute(self) -> None:
metric_service = MetricsService()
content = CourseContent.objects.get(id=self.content_id)
if not content.quiz:
raise QuizNotFoundError(
Expand Down Expand Up @@ -53,3 +55,8 @@ def execute(self) -> None:
)

email_service.send(email_message)
metric_service.quiz_sent(
course_slug=content.course.slug,
organization_id=content.course.organization.id,
quiz_id=quiz.id,
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
DeactivationReason,
)
from django.utils import timezone
from django_email_learning.services.metrics_service import MetricsService
from django_email_learning.services.command_models.abstract_command import (
AbstractCommand,
)
Expand All @@ -23,6 +24,7 @@ class UnsubscribeCommand(AbstractCommand):
organization_id: int

def execute(self) -> None:
metric_service = MetricsService()
try:
course = Course.objects.get(
slug=self.course_slug, organization_id=self.organization_id
Expand Down Expand Up @@ -66,3 +68,8 @@ def execute(self) -> None:
deactivation_reason=DeactivationReason.CANCELED,
final_state_at=timezone.now(),
)
metric_service.user_enrollment_deactivated(
course_slug=course.slug,
organization_id=course.organization.id,
reason=DeactivationReason.CANCELED,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
InvalidVerificationCodeError,
)
from django_email_learning.services.email_sender_service import EmailSenderService
from django_email_learning.services.metrics_service import MetricsService
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
Expand All @@ -24,6 +25,7 @@ class VerifyEnrollmentCommand(AbstractCommand):
verification_code: str = Field(..., pattern=r"^\d{6}$")

def execute(self) -> None:
metric_service = MetricsService()
try:
enrollment = Enrollment.objects.get(
id=self.enrollment_id, status=EnrollmentStatus.UNVERIFIED
Expand Down Expand Up @@ -58,6 +60,9 @@ def execute(self) -> None:
self.logger.info(
f"Content Delivery Scheduled: First content delivery scheduled for Enrollment ID {self.enrollment_id}"
)
metric_service.user_enrollment_activated(
enrollment.course.slug, enrollment.course.organization.id
)

# Send confirmation email
email_service = EmailSenderService()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from django_email_learning.ports.metric_recorder_protocol import MetricRecorderProtocol
from django_email_learning.models import DeactivationReason
import logging

logger = logging.getLogger(__name__)


class LogBasedMetricRecorder(MetricRecorderProtocol):
def user_enrolled_in_course(self, course_slug: str, organization_id: int) -> None:
logger.info(
"User enrolled",
extra={"course_slug": course_slug, "organization_id": organization_id},
)

def quiz_sent(self, course_slug: str, organization_id: int, quiz_id: int) -> None:
logger.info(
"Quiz sent",
extra={
"course_slug": course_slug,
"organization_id": organization_id,
"quiz_id": quiz_id,
},
)

def lesson_sent(
self, course_slug: str, organization_id: int, lesson_id: int
) -> None:
logger.info(
"Lesson sent",
extra={
"course_slug": course_slug,
"organization_id": organization_id,
"lesson_id": lesson_id,
},
)

def user_enrollment_activated(self, course_slug: str, organization_id: int) -> None:
logger.info(
"User enrollment activated",
extra={"course_slug": course_slug, "organization_id": organization_id},
)

def user_enrollment_deactivated(
self, course_slug: str, organization_id: int, reason: DeactivationReason
) -> None:
logger.info(
"User enrollment deactivated",
extra={
"course_slug": course_slug,
"organization_id": organization_id,
"reason": reason,
},
)

def user_completed_course(self, course_slug: str, organization_id: int) -> None:
logger.info(
"User completed course",
extra={"course_slug": course_slug, "organization_id": organization_id},
)

def quiz_submitted(
self, course_slug: str, organization_id: int, quiz_id: int, is_passed: bool
) -> None:
logger.info(
"Quiz submitted",
extra={
"course_slug": course_slug,
"organization_id": organization_id,
"quiz_id": quiz_id,
"is_passed": is_passed,
},
)

def method_executed(self, method_name: str, execution_time: int) -> None:
logger.info(
"Method executed",
extra={"method_name": method_name, "execution_time": execution_time},
)
7 changes: 6 additions & 1 deletion django_email_learning/services/email_sender_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ def __init__(self) -> None:
settings, "DJANGO_EMAIL_LEARNING", {}
)
try:
self.email_sender = import_string(
configured_email_sender = import_string(
DJANGO_EMAIL_LEARNING_SETTINGS["EMAIL_SENDER"]
)
self.email_sender = (
configured_email_sender()
if isinstance(configured_email_sender, type)
else configured_email_sender
)
except KeyError:
from django_email_learning.services.defaults.email_sender import (
DjangoEmailSender,
Expand Down
Loading