-
Notifications
You must be signed in to change notification settings - Fork 70
feat: add GradeEventContextEnricher pipeline step for grade analytics #2543
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,4 @@ | |
| Your project description goes here. | ||
| """ | ||
|
|
||
| __version__ = "8.0.14" | ||
| __version__ = "8.0.15" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """ | ||
| Filter pipeline step implementations for edx-enterprise openedx-filters integrations. | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| """ | ||
| Pipeline step for enriching grade analytics event context. | ||
| """ | ||
| import copy | ||
| from typing import Any | ||
|
|
||
| from openedx_filters.filters import PipelineStep | ||
|
|
||
| from enterprise.models import EnterpriseCourseEnrollment | ||
|
|
||
|
|
||
| class GradeEventContextEnricher(PipelineStep): | ||
| """ | ||
| Enriches a grade analytics event context dict with the learner's enterprise UUID. | ||
|
|
||
| This step is intended to be registered as a pipeline step for the | ||
| ``org.openedx.learning.grade.context.requested.v1`` filter. | ||
|
|
||
| If the user is enrolled in the given course through an enterprise, the enterprise | ||
| UUID is added to the context under the key ``"enterprise_uuid"``. If the user has | ||
| no enterprise course enrollment, the context is returned unchanged. | ||
| """ | ||
|
|
||
| def run_filter(self, context: dict, user_id: int, course_id: str) -> dict[str, Any]: # pylint: disable=arguments-differ | ||
| """ | ||
| Add enterprise UUID to the event context if the user has an enterprise enrollment. | ||
|
|
||
| Arguments: | ||
| context (dict): the event tracking context dict. | ||
| user_id (int): the ID of the user whose grade event is being emitted. | ||
| course_id (str): the course key for the grade event. | ||
|
|
||
| Returns: | ||
| dict: updated pipeline data with the enriched ``context`` dict:: | ||
|
|
||
| { | ||
| "context": <enriched context>, | ||
| "user_id": <unchanged>, | ||
| "course_id": <unchanged>, | ||
| } | ||
| """ | ||
| uuids = EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course(user_id, course_id) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please start this pipeline step with a log line to mirror all the other enterprise pipeline steps and improve stage/prod testing experience. Examples:
Be sure to not log PII. In this case, probably just the user_id and course_id are sufficient (exclude the context to keep log size down). |
||
| if uuids: | ||
| context = copy.deepcopy(context) # create a copy to avoid altering the passed-in value. | ||
| # Warning: Selecting the first element is not likely deterministic! | ||
| context["enterprise_uuid"] = str(uuids[0]) | ||
| return {"context": context, "user_id": user_id, "course_id": course_id} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Tests for enterprise filter pipeline steps.""" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| """ | ||
| Tests for enterprise.filters.grades pipeline step. | ||
| """ | ||
| import uuid | ||
| from unittest.mock import patch | ||
|
|
||
| from django.test import TestCase | ||
|
|
||
| from enterprise.filters.grades import GradeEventContextEnricher | ||
|
|
||
|
|
||
| class TestGradeEventContextEnricher(TestCase): | ||
| """ | ||
| Tests for GradeEventContextEnricher pipeline step. | ||
| """ | ||
|
|
||
| def _make_step(self): | ||
| return GradeEventContextEnricher( | ||
| filter_type="org.openedx.learning.grade.context.requested.v1", | ||
| running_pipeline=[], | ||
| ) | ||
|
kiram15 marked this conversation as resolved.
|
||
|
|
||
| @patch("enterprise.models.EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course") | ||
| def test_enriches_context_when_enterprise_enrollment_found(self, mock_get_uuids): | ||
| """ | ||
| When an enterprise course enrollment exists, enterprise_uuid is added to context. | ||
| """ | ||
| enterprise_uuid = uuid.uuid4() | ||
| mock_get_uuids.return_value = [enterprise_uuid] | ||
|
|
||
| step = self._make_step() | ||
| context = {"org": "TestOrg", "course_id": "course-v1:org+course+run"} | ||
| result = step.run_filter(context=context, user_id=7, course_id="course-v1:org+course+run") | ||
|
|
||
| assert result == { | ||
| "context": {**context, "enterprise_uuid": str(enterprise_uuid)}, | ||
| "user_id": 7, | ||
| "course_id": "course-v1:org+course+run", | ||
| } | ||
| mock_get_uuids.assert_called_once_with(7, "course-v1:org+course+run") | ||
|
|
||
| @patch("enterprise.models.EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course") | ||
| def test_returns_unchanged_context_when_no_enterprise_enrollment(self, mock_get_uuids): | ||
| """ | ||
| When no enterprise course enrollment exists, context is returned unchanged. | ||
| """ | ||
| mock_get_uuids.return_value = [] | ||
|
|
||
| step = self._make_step() | ||
| context = {"org": "TestOrg"} | ||
| result = step.run_filter(context=context, user_id=99, course_id="course-v1:org+course+run") | ||
|
|
||
| assert result == { | ||
| "context": context, | ||
| "user_id": 99, | ||
| "course_id": "course-v1:org+course+run", | ||
| } | ||
| assert "enterprise_uuid" not in result["context"] | ||
|
|
||
| @patch("enterprise.models.EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course") | ||
| def test_uses_first_uuid_when_multiple_enrollments(self, mock_get_uuids): | ||
| """ | ||
| When multiple enterprise enrollments exist, only the first UUID is used. | ||
| """ | ||
| first_uuid = uuid.uuid4() | ||
| second_uuid = uuid.uuid4() | ||
| mock_get_uuids.return_value = [first_uuid, second_uuid] | ||
|
|
||
| step = self._make_step() | ||
| context = {} | ||
| result = step.run_filter(context=context, user_id=1, course_id="course-v1:x+y+z") | ||
|
|
||
| assert result["context"]["enterprise_uuid"] == str(first_uuid) | ||
Uh oh!
There was an error while loading. Please reload this page.