docs: add Open edX Events & Filters latest documentation

This commit is contained in:
Maria Grimaldi
2022-01-28 17:38:18 -04:00
parent d5f8fef029
commit 033934e896
4 changed files with 375 additions and 177 deletions

View File

@@ -140,6 +140,9 @@ Here are the different integration points that python plugins can use:
* - Open edX Events
- Adopt, Stable
- Events are part of the greater Hooks Extension Framework for open extension of edx-platform. Events are a stable way for plugin developers to react to learner or author events. They are defined by a `separate events library`_ that developers can include in their requirements to develop and test the code without creating a dependency on this large repo. For more information see the `hooks guide`_.
* - Open edX Filters
- Adopt, Stable
- Filters are also part of Hooks Extension Framework for open extension of edx-platform. Filters are a flexible way for plugin developers to modify learner or author application flows. They are defined by a `separate filters library`_ that developers can include in their requirements to develop and test the code without creating a dependency on this large repo. For more information see the `hooks guide`_.
.. _Application: https://docs.djangoproject.com/en/3.0/ref/applications/
.. _Django app plugin documentation: https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/plugins/README.rst
@@ -155,6 +158,7 @@ Here are the different integration points that python plugins can use:
.. |pluggable_override docstring| replace:: ``pluggable_override`` docstring
.. _pluggable_override docstring: https://github.com/edx/edx-django-utils/blob/master/edx_django_utils/plugins/pluggable_override.py
.. _separate events library: https://github.com/eduNEXT/openedx-events/
.. _separate filters library: https://github.com/eduNEXT/openedx-filters/
.. _hooks guide: https://github.com/edx/edx-platform/blob/master/docs/guides/hooks/index.rst
Platform Look & Feel

View File

@@ -0,0 +1,179 @@
Open edX Events
===============
How to use
----------
Using openedx-events in your code is very straight forward. We can consider the
two possible cases, sending or receiving an event.
Receiving events
^^^^^^^^^^^^^^^^
This is one of the most common use cases for plugins. The edx-platform will send
and event and you want to react to it in your plugin.
For this you need to:
1. Include openedx-events in your dependencies.
2. Connect your receiver functions to the signals being sent.
Connecting signals can be done using regular django syntax:
.. code-block:: python
from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED
@receiver(SESSION_LOGIN_COMPLETED)
# your receiver function here
Or at the apps.py
.. code-block:: python
"signals_config": {
"lms.djangoapp": {
"relative_path": "your_module_name",
"receivers": [
{
"receiver_func_name": "your_receiver_function",
"signal_path": "openedx_events.learning.signals.SESSION_LOGIN_COMPLETED",
},
],
}
},
In case you are listening to an event in the edx-platform repo, you can directly
use the django syntax since the apps.py method will not be available without the
plugin.
Sending events
^^^^^^^^^^^^^^
Sending events requires you to import both the event definition as well as the
attr data classes that encapsulate the event data.
.. code-block:: python
from openedx_events.learning.data import UserData, UserPersonalData
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
STUDENT_REGISTRATION_COMPLETED.send_event(
user=UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
)
You can do this both from the edx-platform code as well as from an openedx
plugin.
Testing events
^^^^^^^^^^^^^^
Testing your code in CI, specially for plugins is now possible without having to
import the complete edx-platform as a dependency.
To test your functions you need to include the openedx-events library in your
testing dependencies and make the signal connection in your test case.
.. code-block:: python
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
def test_your_receiver(self):
STUDENT_REGISTRATION_COMPLETED.connect(your_function)
STUDENT_REGISTRATION_COMPLETED.send_event(
user=UserData(
pii=UserPersonalData(
username='test_username',
email='test_email@example.com',
name='test_name',
),
id=1,
is_active=True,
),
)
# run your assertions
Changes in the openedx-events library that are not compatible with your code
should break this kind of test in CI and let you know you need to upgrade your
code.
Live example
^^^^^^^^^^^^
For a complete and detailed example you can see the `openedx-events-2-zapier`_
plugin. This is a fully functional plugin that connects to
``STUDENT_REGISTRATION_COMPLETED`` and ``COURSE_ENROLLMENT_CREATED`` and sends
the relevant information to zapier.com using a webhook.
.. _openedx-events-2-zapier: https://github.com/eduNEXT/openedx-events-2-zapier
Index of Events
-----------------
This list contains the events currently being sent by edx-platform. The provided
links target both the definition of the event in the openedx-events library as
well as the trigger location in this same repository.
.. list-table::
:widths: 35 50 20
* - *Name*
- *Type*
- *Date added*
* - `STUDENT_REGISTRATION_COMPLETED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L24>`_
- org.openedx.learning.student.registration.completed.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/user_authn/views/register.py#L262>`_
* - `SESSION_LOGIN_COMPLETED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L36>`_
- org.openedx.learning.auth.session.login.completed.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/user_authn/views/login.py#L320>`_
* - `COURSE_ENROLLMENT_CREATED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L48>`_
- org.openedx.learning.course.enrollment.created.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1671>`_
* - `COURSE_ENROLLMENT_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L60>`_
- org.openedx.learning.course.enrollment.changed.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1430>`_
* - `COURSE_UNENROLLMENT_COMPLETED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L72>`_
- org.openedx.learning.course.unenrollment.completed.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1457>`_
* - `CERTIFICATE_CREATED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L84>`_
- org.openedx.learning.certificate.created.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/lms/djangoapps/certificates/models.py#L514>`_
* - `CERTIFICATE_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L94>`_
- org.openedx.learning.certificate.changed.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/lms/djangoapps/certificates/models.py#L482>`_
* - `CERTIFICATE_REVOKED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L108>`_
- org.openedx.learning.certificate.revoked.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/lms/djangoapps/certificates/models.py#L402>`_
* - `COHORT_MEMBERSHIP_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L120>`_
- org.openedx.learning.cohort_membership.changed.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/course_groups/models.py#L166>`_
* - `COURSE_DISCUSSIONS_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L132>`_
- org.openedx.learning.discussions.configuration.changed.v1
- `2022-06-14 <https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/discussions/tasks.py#L30>`_

View File

@@ -0,0 +1,183 @@
Open edX Filters
================
How to use
----------
Using openedx-filters in your code is very straight forward. We can consider the
two possible cases:
Configuring a filter
^^^^^^^^^^^^^^^^^^^^
Implement pipeline steps
************************
Let's say you want to consult student's information with a third party service
before generating the students certificate. This is a common use case for filters,
where the functions part of the filter's pipeline will perform the consulting tasks and
decide the execution flow for the application. These functions are the pipeline steps,
and can be implemented in an installable Python library:
.. code-block:: python
# Step implementation taken from openedx-filters-samples plugin
from openedx_filters import PipelineStep
from openedx_filters.learning.filters import CertificateCreationRequested
class StopCertificateCreation(PipelineStep):
def run_filter(self, user, course_id, mode, status):
# Consult third party service and check if continue
# ...
# User not in third party service, denied certificate generation
raise CertificateCreationRequested.PreventCertificateCreation(
"You can't generate a certificate from this site."
)
There's two key components to the implementation:
1. The filter step must be a subclass of ``PipelineStep``.
2. The ``run_filter`` signature must match the filters definition, eg.,
the previous step matches the method's definition in CertificateCreationRequested.
Attach/hook pipeline to filter
******************************
After implementing the pipeline steps, we have to tell the certificate creation
filter to execute our pipeline.
.. code-block:: python
OPEN_EDX_FILTERS_CONFIG = {
"org.openedx.learning.certificate.creation.requested.v1": {
"fail_silently": False,
"pipeline": [
"openedx_filters_samples.samples.pipeline.StopCertificateCreation"
]
},
}
Triggering a filter
^^^^^^^^^^^^^^^^^^^
In order to execute a filter in your own plugin/library, you must install the
plugin where the steps are implemented and also, ``openedx-filters``.
.. code-block:: python
# Code taken from lms/djangoapps/certificates/generation_handler.py
from openedx_filters.learning.filters import CertificateCreationRequested
try:
self.user, self.course_id, self.mode, self.status = CertificateCreationRequested.run_filter(
user=self.user, course_id=self.course_id, mode=self.mode, status=self.status,
)
except CertificateCreationRequested.PreventCertificateCreation as exc:
raise CertificateGenerationNotAllowed(str(exc)) from exc
Testing filters' steps
^^^^^^^^^^^^^^^^^^^^^^
It's pretty straightforward to test your pipeline steps, you'll need to include the
``openedx-filters`` library in your testing dependencies and configure them in your test case.
.. code-block:: python
from openedx_filters.learning.filters import CertificateCreationRequested
@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.certificate.creation.requested.v1": {
"fail_silently": False,
"pipeline": [
"openedx_filters_samples.samples.pipeline.StopCertificateCreation"
]
}
}
)
def test_certificate_creation_requested_filter(self):
"""
Test filter triggered before the certificate creation process starts.
Expected results:
- The pipeline step configured for the filter raises PreventCertificateCreation
when the conditions are met.
"""
with self.assertRaises(CertificateCreationRequested.PreventCertificateCreation):
CertificateCreationRequested.run_filter(
user=self.user, course_key=self.course_key, mode="audit",
)
# run your assertions
Changes in the ``openedx-filters`` library that are not compatible with your code
should break this kind of test in CI and let you know you need to upgrade your code.
The main limitation while testing filters' steps it's their arguments, as they are edxapp
memory objects, but that can be solved in CI using Python mocks.
Live example
^^^^^^^^^^^^
For filter steps samples you can visit the `openedx-filters-samples`_ plugin, where
you can find minimal steps exemplifying the different ways on how to use
``openedx-filters``.
.. _openedx-filters-samples: https://github.com/eduNEXT/openedx-filters-samples
Index of Filters
-----------------
This list contains the filters currently being executed by edx-platform. The provided
links target both the definition of the filter in the openedx-filters library as
well as the trigger location in this same repository.
.. list-table::
:widths: 35 50 20
* - *Name*
- *Type*
- *Date added*
* - `StudentRegistrationRequested <https://github.com/eduNEXT/openedx-filters/blob/main/openedx_filters/learning/filters.py#L9>`_
- org.openedx.learning.student.registration.requested.v1
- `2022-06-14 <https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/user_authn/views/register.py#L261>`_
* - `StudentLoginRequested <https://github.com/eduNEXT/openedx-filters/blob/main/openedx_filters/learning/filters.py#L40>`_
- org.openedx.learning.student.login.requested.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/user_authn/views/login.py#L569>`_
* - `CourseEnrollmentStarted <https://github.com/eduNEXT/openedx-filters/blob/main/openedx_filters/learning/filters.py#L70>`_
- org.openedx.learning.course.enrollment.started.v1
- `2022-06-14 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1675>`_
* - `CourseUnenrollmentStarted <https://github.com/eduNEXT/openedx-filters/blob/main/openedx_filters/learning/filters.py#L98>`_
- org.openedx.learning.course.unenrollment.started.v1
- `2022-06-14 https://github.com/eduNEXT/edx-platform/blob/master/common/djangoapps/student/models.py#L1752>`_
* - `CertificateCreationRequested <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L142>`_
- org.openedx.learning.certificate.creation.requested.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/lms/djangoapps/certificates/generation_handler.py#L119>`_
* - `CertificateRenderStarted <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L161>`_
- org.openedx.learning.certificate.render.started.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/lms/djangoapps/certificates/views/webview.py#L649>`_
* - `CohortChangeRequested <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L230>`_
- org.openedx.learning.cohort.change.requested.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/openedx/core/djangoapps/course_groups/models.py#L138>`_
* - `CohortAssignmentRequested <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L256>`_
- org.openedx.learning.cohort.assignment.requested.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/openedx/core/djangoapps/course_groups/models.py#L149>`_
* - `CourseAboutRenderStarted <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L281>`_
- org.openedx.learning.course_about.render.started.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/lms/djangoapps/courseware/views/views.py#L1015>`_
* - `DashboardRenderStarted <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L354>`_
- org.openedx.learning.dashboard.render.started.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/common/djangoapps/student/views/dashboard.py#L878>`_

View File

@@ -22,8 +22,8 @@ before this data is put back in the original application flow. In order to allow
extension developers to use the Events and Filters definitions on their plugins,
both kinds of hooks are defined in lightweight external libraries.
* `openedx filters`_
* `openedx events`_
* `openedx-filters`_
* `openedx-events`_
Hooks are designed with stability in mind. The main goal is that developers can
use them to change the functionality of the platform as needed and still be able
@@ -36,8 +36,8 @@ A longer description of the framework and it's history can be found in `OEP 50`_
.. _OEP 50: https://open-edx-proposals.readthedocs.io/en/latest/oep-0050-hooks-extension-framework.html
.. _versioning ADR: https://github.com/eduNEXT/openedx-events/blob/main/docs/decisions/0002-events-naming-and-versioning.rst
.. _payload ADR: https://github.com/eduNEXT/openedx-events/blob/main/docs/decisions/0003-events-payload.rst
.. _openedx filters: https://github.com/eduNEXT/openedx-filters
.. _openedx events: https://github.com/eduNEXT/openedx-events
.. _openedx-filters: https://github.com/eduNEXT/openedx-filters
.. _openedx-events: https://github.com/eduNEXT/openedx-events
On the technical side events are implemented through django signals which makes
them run in the same python process as the lms or cms. Furthermore, events block
@@ -45,176 +45,8 @@ the running process. Listeners of an event are encouraged to monitor the
performance or use alternative arch patterns such as receiving the event and
defer to launching async tasks than do the slow processing.
How to use
----------
Using openedx-events in your code is very straight forward. We can consider the
two possible cases, sending or receiving an event.
Receiving events
^^^^^^^^^^^^^^^^
This is one of the most common use cases for plugins. The edx-platform will send
and event and you want to react to it in your plugin.
For this you need to:
1. Include openedx-events in your dependencies.
2. Connect your receiver functions to the signals being sent.
Connecting signals can be done using regular django syntax:
.. code-block:: python
from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED
@receiver(SESSION_LOGIN_COMPLETED)
# your receiver function here
Or at the apps.py
.. code-block:: python
"signals_config": {
"lms.djangoapp": {
"relative_path": "your_module_name",
"receivers": [
{
"receiver_func_name": "your_receiver_function",
"signal_path": "openedx_events.learning.signals.SESSION_LOGIN_COMPLETED",
},
],
}
},
In case you are listening to an event in the edx-platform repo, you can directly
use the django syntax since the apps.py method will not be available without the
plugin.
Sending events
^^^^^^^^^^^^^^
Sending events requires you to import both the event definition as well as the
attr data classes that encapsulate the event data.
.. code-block:: python
from openedx_events.learning.data import UserData, UserPersonalData
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
STUDENT_REGISTRATION_COMPLETED.send_event(
user=UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
)
You can do this both from the edx-platform code as well as from an openedx
plugin.
Testing events
^^^^^^^^^^^^^^
Testing your code in CI, specially for plugins is now possible without having to
import the complete edx-platform as a dependency.
To test your functions you need to include the openedx-events library in your
testing dependencies and make the signal connection in your test case.
.. code-block:: python
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
def test_your_receiver(self):
STUDENT_REGISTRATION_COMPLETED.connect(your_function)
STUDENT_REGISTRATION_COMPLETED.send_event(
user=UserData(
pii=UserPersonalData(
username='test_username',
email='test_email@example.com',
name='test_name',
),
id=1,
is_active=True,
),
)
# run your assertions
Changes in the openedx-events library that are not compatible with your code
should break this kind of test in CI and let you know you need to upgrade your
code.
Live example
^^^^^^^^^^^^
For a complete and detailed example you can see the `openedx-events-2-zapier`_
plugin. This is a fully functional plugin that connects to
``STUDENT_REGISTRATION_COMPLETED`` and ``COURSE_ENROLLMENT_CREATED`` and sends
the relevant information to zapier.com using a webhook.
.. _openedx-events-2-zapier: https://github.com/eduNEXT/openedx-events-2-zapier
Index of Events
-----------------
This list contains the events currently being sent by edx-platform. The provided
links target both the definition of the event in the openedx-events library as
well as the trigger location in this same repository.
.. list-table::
:widths: 35 50 20
* - *Name*
- *Type*
- *Date added*
* - `STUDENT_REGISTRATION_COMPLETED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L18>`_
- org.openedx.learning.student.registration.completed.v1
- `2021-09-02 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/user_authn/views/register.py#L258>`__
* - `SESSION_LOGIN_COMPLETED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L30>`_
- org.openedx.learning.auth.session.login.completed.v1
- `2021-09-02 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/user_authn/views/login.py#L306>`__
* - `COURSE_ENROLLMENT_CREATED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L42>`_
- org.openedx.learning.course.enrollment.created.v1
- `2021-09-02 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1675>`__
* - `COURSE_ENROLLMENT_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L54>`_
- org.openedx.learning.course.enrollment.changed.v1
- `2021-09-22 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1675>`__
* - `COURSE_UNENROLLMENT_COMPLETED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L66>`_
- org.openedx.learning.course.unenrollment.completed.v1
- `2021-09-22 <https://github.com/edx/edx-platform/blob/master/common/djangoapps/student/models.py#L1468>`__
* - `CERTIFICATE_CREATED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L78>`_
- org.openedx.learning.certificate.created.v1
- `2021-09-22 <https://github.com/edx/edx-platform/blob/master/lms/djangoapps/certificates/models.py#L506>`__
* - `CERTIFICATE_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L90>`_
- org.openedx.learning.certificate.changed.v1
- `2021-09-22 <https://github.com/edx/edx-platform/blob/master/lms/djangoapps/certificates/models.py#L475>`__
* - `CERTIFICATE_REVOKED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L102>`_
- org.openedx.learning.certificate.revoked.v1
- `2021-09-22 <https://github.com/edx/edx-platform/blob/master/lms/djangoapps/certificates/models.py#L397>`__
* - `COHORT_MEMBERSHIP_CHANGED <https://github.com/eduNEXT/openedx-events/blob/main/openedx_events/learning/signals.py#L114>`_
- org.openedx.learning.cohort_membership.changed.v1
- `2021-09-22 <https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/course_groups/models.py#L135>`__
On the other hand, filters are implemented using a pipeline mechanism, that executes
a list of functions called ``steps`` configured through Django settings. Each
pipeline step receives a dictionary with data, process it and returns an output. During
this process, they can alter the application execution flow by halting the process
or modifying their input arguments.