How to listen to openedX events and perform actions

Hi,
I would like to extend the functionality of my installation as follows:
When a certain event happens (eg when the user receives a certificate for a course), I would like to run some custom queries to the DB and make a POST request to another server.

This would be done by listening to one of openedX’s events and then connecting to the database to retrieve the data based on the event parameters.

As I understand, I should create a new Tutor plugin that does the things above, and integrate it into my installation.

I have created a new plugin (based on the cookiecutter installation scaffolding), but I don’t know where to look next.

Has anybody faced any similar problems before?

1 Like

Hi @PavlosIsaris! This is an interesting idea. In edx-platform, events are handled by the event-tracking library: https://github.com/edx/event-tracking
Events are forwarded to one or more tracking backends, as defined in the edx-platform EVENT_TRACKING_BACKENDS setting: https://github.com/edx/edx-platform/blob/open-release/ironwood.master/lms/envs/common.py#L765

You will need to create a custom backend – basically, it’s a class that implements a send(self, event) method, as examplified by the LoggerBackend class: https://github.com/edx/event-tracking/blob/master/eventtracking/backends/logger.py#L15

Then, you will have to install your event tracking backend in the openedx container image (with extra requirements https://docs.tutor.overhang.io/configuration.html#installing-extra-xblocks-and-requirements).

Success and happiness will follow inevitably.

Good luck!

@regis thanks for the answer and for trying to help.
I have done the following:

  1. Created a Python project with a setup.py file and an EventsHandler.py class, inside a directory. The structure is:
events_handler/
    EventsHandlerpy
setup.py

The setup.py:


"""Setup for poll XBlock."""

from __future__ import absolute_import
import os
from setuptools import setup


def package_data(pkg, roots):
    """
    Generic function to find package_data.

    All of the files under each of the `roots` will be declared as package
    data for package `pkg`.
    """
    data = []
    for root in roots:
        for dirname, _, files in os.walk(os.path.join(pkg, root)):
            for fname in files:
                data.append(os.path.relpath(os.path.join(dirname, fname), pkg))

    return {pkg: data}


setup(
    name='events_handler',
    version='0.0.1',
    description='An events handler for the project.',
    packages=[
        'events_handler',
    ],
    install_requires=[
    ],
    entry_points={
        'events_handler.v1': [
            'events_handler = events_handler:EventsHandler',
        ]
    },
    package_data=package_data("events_handler", []),
)

  1. In the EventsHandler.py, I have a class that uses the send method, as you said:
...
    def send(self, event):
        """Send the event to the standard python logger"""
        event_str = json.dumps(event, cls=DateTimeJSONEncoder)

        # TODO: do something smarter than simply dropping the event on
        # the floor.
        if self.max_event_size is None or len(event_str) <= self.max_event_size:
            self.log("My Events Handler works!");
            self.log(event_str)
...

I only added an additional self.log call, in order to see if it works.

This repository now exists in .local/share/tutor/env/build/openedx/requirements/my-events-handler directory.

  1. I have also edited the .local/share/tutor/env/build/openedx/requirements/private.txt to include the following:
# Add your additional requirements, such as xblocks, to this file. For
# requirements coming from private repositories, clone the repository to this
# folder and then add your requirement with the `-e` flag. Ex:
#
#   git clone git@myserver:myprivaterepo.git
#   echo "-e ./myprivaterepo/" >> private.txt-e ./openedx-ensoed-events-handler
-e ./my-events-handler
  1. I then run tutor images build openedx and tutor local start, but as I can see from the logging file, all the logs are only logged once, just as before (I don’t see the logs from my EventsHandler.py).

Do you have any idea on what I am doing wrong?
Should I add anything to the EVENT_TRACKING_BACKENDS settings?

Thanks in advance.

Yes of course! You should add your EventsHandler.

@regis And what should the ENGINE and processors fields BE?

As I can see from the EVENT_TRACKING_BACKENDS, There are 2 entries, tracking_logs and segmentio.
At which level should I add my EventsHandler and with which fields?

I have no idea, I did not look at the source code for the processors of the LoggerBackend. I would say that if you are happy with the LoggerBackend processors then just the same ones. If not, you should use another one or create your own.

In the tracking_logs entry.

@regis, OK, I think I got it.
And where exactly is this common.py file in my Tutor installation? Is it in
.local/share/tutor/env/apps/openedx/settings/lms/common.py ?
Because there is no EVENT_TRACKING_BACKENDS entry there.

The edx-platform/lms/envs/common.py and edx-platform/cms/envs/common.py files are imported by the custom tutor settings, both in development and production. Are you familiar with python syntax?

@regis I am not sure exactly how I should modify the tracking_logs object in the EVENT_TRACKING_BACKENDS.
Right now, it looks like this:

'tracking_logs': {
          'ENGINE': 'eventtracking.backends.routing.RoutingBackend',
          'OPTIONS': {
              'backends': {
                  'logger': {
                      'ENGINE': 'eventtracking.backends.logger.LoggerBackend',
                      'OPTIONS': {
                          'name': 'tracking',
                          'max_event_size': TRACK_MAX_EVENT,
                      }
                  },
                  'events_handler': {
                    'ENGINE': 'events_handler.EventsHandler',
                      'OPTIONS': {
                          'name': 'tracking',
                          'max_event_size': TRACK_MAX_EVENT,
                      }
                  }
              },
              'processors': [
                  {'ENGINE': 'track.shim.LegacyFieldMappingProcessor'},
                  {'ENGINE': 'track.shim.PrefixedEventProcessor'}
              ]
          }
      }

As you can see, I added an events_handler entry, after the logger.
But I get an error Cannot find class events_handler.EventsHandler.

In setup.py of my custom backend, I have the following:

setup(
    name='events_handler',
    version='0.0.1',
    description='An events handler for ENSOED.',
    packages=[
        'events_handler',
    ],
    install_requires=[
    ],
    entry_points={
        'events_handler.v1': [
            'events_handler = events_handler:EventsHandler',
        ]
    },
    package_data=package_data("events_handler", []),
)

Am I referencing my class in the wrong way?

Yes, most probably, although I have no way to verify this for you. This is why I asked you earlier how familiar you are with python syntax.

At this point, you know more about event handlers than myself. Unfortunately I don’t have enough free time to help you create what you need – not without billing you for my time. If you are interested in hiring me for consulting work, please send me a private message. Sorry, I hope you understand :-/