Skip to content

feat: add GradeEventContextEnricher pipeline step for grade analytics#2543

Draft
pwnage101 wants to merge 1 commit intomasterfrom
pwnage101/ENT-11563
Draft

feat: add GradeEventContextEnricher pipeline step for grade analytics#2543
pwnage101 wants to merge 1 commit intomasterfrom
pwnage101/ENT-11563

Conversation

@pwnage101
Copy link
Copy Markdown
Contributor

@pwnage101 pwnage101 commented Mar 4, 2026

Introduces enterprise/filters/grades.py with GradeEventContextEnricher, a PipelineStep for the org.openedx.learning.grade.context.requested.v1 filter that enriches grade analytics event context with the learner's enterprise UUID. Adds openedx-filters as a dependency and unit tests covering both enrichment and no-op branches.

ENT-11563

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com


Blocked by:

Blocks:

Introduces enterprise/filters/grades.py with GradeEventContextEnricher,
a PipelineStep for the org.openedx.learning.grade.context.requested.v1
filter that enriches grade analytics event context with the learner's
enterprise UUID. Adds openedx-filters as a dependency and unit tests
covering both enrichment and no-op branches.

ENT-11563

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor Author

@pwnage101 pwnage101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from these changes, please also remember to bump the version of edx-enterprise.

Apologies if this wasn't clear, but these draft claude-generated PRs were meant to offer guidance and direction, not to be ready to go as-is.

no enterprise course enrollment, the context is returned unchanged.
"""

def run_filter(self, context, user_id, course_id): # pylint: disable=arguments-differ
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add python type hints

Suggested change
def run_filter(self, context, user_id, course_id): # pylint: disable=arguments-differ
def run_filter(self, context: dict, user_id: int, course_id: str) -> dict[str, Any]: # pylint: disable=arguments-differ

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 or CourseKey): the course key for the grade event.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the filter definition, it's impossible for course_id to be a CourseKey: https://github.com/openedx/openedx-filters/blob/d4b09fdb850436bbcea638066c124a47b5025084/openedx_filters/learning/filters.py#L1584

Fixed docstring:

Suggested change
course_id (str or CourseKey): the course key for the grade event.
course_id (str): the course key for the grade event.

course_id (str or CourseKey): the course key for the grade event.

Returns:
dict: updated pipeline data with the enriched ``context`` dict.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be more specific about the shape of the output dict.

Suggested change
dict: updated pipeline data with the enriched ``context`` dict.
dict: updated pipeline data with the enriched ``context`` dict.
{
"context": <enriched context>,
"user_id": <unchanged>,
"course_id": <unchanged>,
}

)
if uuids:
return {"context": {**context, "enterprise_uuid": str(uuids[0])}}
return {"context": context}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing passthrough values and should probably just utilize updated_context

Suggested change
return {"context": context}
return {"context": context, "user_id": user_id, "course_id": course_id}

str(course_id),
)
if uuids:
return {"context": {**context, "enterprise_uuid": str(uuids[0])}}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This lacks passthrough values, 2. probably shouldn't just be an early-return since that's less maintainable, and 3. leverages complicated destructuring instead of something more pythonic and something which allows us to retain one exit point of the function. Solve all 3 problems at once:
Suggested change
return {"context": {**context, "enterprise_uuid": str(uuids[0])}}
context = 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])

Comment thread requirements/test.txt
# via -r requirements/test-master.txt
openedx-events==10.5.0
# via -r requirements/test-master.txt
openedx-filters==2.1.0
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ 3.3.0 now supports python 3.11. We should take this opportunity to deploy with the latest version.

Suggested change
openedx-filters==2.1.0
openedx-filters==3.3.0

Ditto for the rest of the requirements changes in this PR.

Comment on lines +33 to +36
uuids = EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course(
str(user_id),
str(course_id),
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. user_id can stay an int. I looked at the function, it's okay.
  2. course_id can be assumed to be a str, that's promised by the type hint.
Suggested change
uuids = EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course(
str(user_id),
str(course_id),
)
uuids = EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course(user_id, course_id)

Comment on lines +23 to +26
@patch(
"enterprise.filters.grades.EnterpriseCourseEnrollment"
".get_enterprise_uuids_with_user_and_course"
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Overly strict line length limits.
  2. Unnecessary patching of module at import (enterprise.filters.grades) instead of source (enterprise.models).
Suggested change
@patch(
"enterprise.filters.grades.EnterpriseCourseEnrollment"
".get_enterprise_uuids_with_user_and_course"
)
@patch("enterprise.models.EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course")

Ditto for other tests.

Comment on lines +18 to +21
return GradeEventContextEnricher(
"org.openedx.learning.grade.context.requested.v1",
[],
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use argument names to improve readability:

Suggested change
return GradeEventContextEnricher(
"org.openedx.learning.grade.context.requested.v1",
[],
)
return GradeEventContextEnricher(
filter_type="org.openedx.learning.grade.context.requested.v1",
running_pipeline=[],
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant