Skip to content

Commit

Permalink
feat: adds events to handle changes in xblocks
Browse files Browse the repository at this point in the history
fix: documentation typo

fix: remove unwanted comments and rename XBlockData

docs: docstrings in tests for usage key serialization

refactor: rename XBlockDuplicatedData to DuplicatedXBlockData

chore: bump version and update changelog

chore: bump version and update changelog

fix: import order

chore: bump version

docs: generalize signal docs

docs: update xblock_published signal docs
  • Loading branch information
navinkarkera committed Jan 3, 2023
1 parent 10da4d4 commit faad90a
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ Change Log
Unreleased
----------

[4.1.0] - 2023-01-03
---------------------
Added
~~~~~~~
* Added new XBLOCK_PUBLISHED, XBLOCK_DUPLICATED and XBLOCK_DELETED signals in content_authoring.
* Added XBlockData and DuplicatedXBlockData classes
* Added custom UsageKeyAvroSerializer for opaque_keys UsageKey.

[4.0.0] - 2022-12-01
--------------------
Changed
Expand Down
2 changes: 1 addition & 1 deletion openedx_events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
more information about the project.
"""

__version__ = "4.0.0"
__version__ = "4.1.0"
30 changes: 29 additions & 1 deletion openedx_events/content_authoring/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from datetime import datetime

import attr
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.keys import CourseKey, UsageKey


@attr.s(frozen=True)
Expand Down Expand Up @@ -54,3 +54,31 @@ class CourseCatalogData:
schedule_data = attr.ib(type=CourseScheduleData)
hidden = attr.ib(type=bool, default=False)
invitation_only = attr.ib(type=bool, default=False)


@attr.s(frozen=True)
class XBlockData:
"""
Data about changed XBlock.
Arguments:
usage_key (UsageKey): identifier of the XBlock object.
block_type (str): type of block.
"""

usage_key = attr.ib(type=UsageKey)
block_type = attr.ib(type=str)


@attr.s(frozen=True)
class DuplicatedXBlockData(XBlockData):
"""
Data about duplicated XBlock.
This class extends XBlockData to include source_usage_key.
Arguments:
source_usage_key (UsageKey): identifier of the source XBlock object.
"""

source_usage_key = attr.ib(type=UsageKey)
43 changes: 42 additions & 1 deletion openedx_events/content_authoring/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
docs/decisions/0003-events-payload.rst
"""

from openedx_events.content_authoring.data import CourseCatalogData
from openedx_events.content_authoring.data import CourseCatalogData, DuplicatedXBlockData, XBlockData
from openedx_events.tooling import OpenEdxPublicSignal

# .. event_type: org.openedx.content_authoring.course.catalog_info.changed.v1
Expand All @@ -21,3 +21,44 @@
"catalog_info": CourseCatalogData,
}
)


# .. event_type: org.openedx.content_authoring.xblock.published.v1
# .. event_name: XBLOCK_PUBLISHED
# .. event_description: Fired when an XBlock is published. If a parent block
# with changes in one or more child blocks is published, only a single
# XBLOCK_PUBLISHED event is fired with parent block details.
# For example: If a section is published with changes in multiple units,
# only a single event is fired with section details like :
# `XBlockData(usage_key="section-usage-key", block_type="chapter")`
# .. event_data: XBlockData
XBLOCK_PUBLISHED = OpenEdxPublicSignal(
event_type="org.openedx.content_authoring.xblock.published.v1",
data={
"xblock_info": XBlockData,
}
)


# .. event_type: org.openedx.content_authoring.xblock.deleted.v1
# .. event_name: XBLOCK_DELETED
# .. event_description: Fired when an XBlock is deleted.
# .. event_data: XBlockData
XBLOCK_DELETED = OpenEdxPublicSignal(
event_type="org.openedx.content_authoring.xblock.deleted.v1",
data={
"xblock_info": XBlockData,
}
)


# .. event_type: org.openedx.content_authoring.xblock.duplicated.v1
# .. event_name: XBLOCK_DUPLICATED
# .. event_description: Fired when an XBlock is duplicated in Studio.
# .. event_data: DuplicatedXBlockData
XBLOCK_DUPLICATED = OpenEdxPublicSignal(
event_type="org.openedx.content_authoring.xblock.duplicated.v1",
data={
"xblock_info": DuplicatedXBlockData,
}
)
23 changes: 21 additions & 2 deletions openedx_events/event_bus/avro/custom_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from abc import ABC, abstractmethod
from datetime import datetime

from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.keys import CourseKey, UsageKey

from openedx_events.event_bus.avro.types import PYTHON_TYPE_TO_AVRO_MAPPING

Expand Down Expand Up @@ -71,4 +71,23 @@ def deserialize(data: str):
return datetime.fromisoformat(data)


DEFAULT_CUSTOM_SERIALIZERS = [CourseKeyAvroSerializer, DatetimeAvroSerializer]
class UsageKeyAvroSerializer(BaseCustomTypeAvroSerializer):
"""
CustomTypeAvroSerializer for UsageKey class.
"""

cls = UsageKey
field_type = PYTHON_TYPE_TO_AVRO_MAPPING[str]

@staticmethod
def serialize(obj) -> str:
"""Serialize obj into string."""
return str(obj)

@staticmethod
def deserialize(data: str):
"""Deserialize string into obj."""
return UsageKey.from_string(data)


DEFAULT_CUSTOM_SERIALIZERS = [CourseKeyAvroSerializer, DatetimeAvroSerializer, UsageKeyAvroSerializer]
5 changes: 4 additions & 1 deletion openedx_events/event_bus/avro/tests/test_avro.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime
from unittest import TestCase

from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.keys import CourseKey, UsageKey

# Each new folder with signals must be manually imported in order for the signals to be cached
# and used in the unit tests. Using 'disable=reimported' with pylint will work,
Expand Down Expand Up @@ -49,6 +49,9 @@ def generate_test_event_data_for_data_type(data_type):
str: "default",
float: 1.0,
CourseKey: CourseKey.from_string("course-v1:edX+DemoX.1+2014"),
UsageKey: UsageKey.from_string(
"block-v1:edx+DemoX+Demo_course+type@video+block@UaEBjyMjcLW65gaTXggB93WmvoxGAJa0JeHRrDThk",
),
datetime: datetime.now(),
}
for attribute in data_type.__attrs_attrs__:
Expand Down
17 changes: 16 additions & 1 deletion openedx_events/event_bus/avro/tests/test_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime
from unittest import TestCase

from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.keys import CourseKey, UsageKey

from openedx_events.event_bus.avro.deserializer import AvroSignalDeserializer
from openedx_events.event_bus.avro.tests.test_utilities import (
Expand Down Expand Up @@ -102,6 +102,21 @@ def test_default_coursekey_deserialization(self):
self.assertIsInstance(course_deserialized, CourseKey)
self.assertEqual(course_deserialized, course_key)

def test_default_usagekey_deserialization(self):
"""
Test deserialization of UsageKey
"""
SIGNAL = create_simple_signal({"usage_key": UsageKey})
deserializer = AvroSignalDeserializer(SIGNAL)
usage_key = UsageKey.from_string(
"block-v1:edx+DemoX+Demo_course+type@video+block@UaEBjyMjcLW65gaTXggB93WmvoxGAJa0JeHRrDThk",
)
as_dict = {"usage_key": str(usage_key)}
event_data = deserializer.from_dict(as_dict)
usage_key_deserialized = event_data["usage_key"]
self.assertIsInstance(usage_key_deserialized, UsageKey)
self.assertEqual(usage_key_deserialized, usage_key)

def test_deserialization_with_custom_serializer(self):
SIGNAL = create_simple_signal({"test_data": NonAttrs})
deserializer = SpecialDeserializer(SIGNAL)
Expand Down
15 changes: 14 additions & 1 deletion openedx_events/event_bus/avro/tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest
from django.test import TestCase
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.keys import CourseKey, UsageKey

from openedx_events.event_bus.avro.serializer import AvroSignalSerializer
from openedx_events.event_bus.avro.tests.test_utilities import (
Expand Down Expand Up @@ -105,6 +105,19 @@ def test_default_coursekey_serialization(self):
data_dict = serializer.to_dict(test_data)
self.assertDictEqual(data_dict, {"course": str(course_key)})

def test_default_usagekey_serialization(self):
"""
Test serialization of UsageKey
"""
SIGNAL = create_simple_signal({"usage_key": UsageKey})
serializer = AvroSignalSerializer(SIGNAL)
usage_key = UsageKey.from_string(
"block-v1:edx+DemoX+Demo_course+type@video+block@UaEBjyMjcLW65gaTXggB93WmvoxGAJa0JeHRrDThk",
)
test_data = {"usage_key": usage_key}
data_dict = serializer.to_dict(test_data)
self.assertDictEqual(data_dict, {"usage_key": str(usage_key)})

def test_serialization_with_custom_serializer(self):
SIGNAL = create_simple_signal({"test_data": NonAttrs})

Expand Down

0 comments on commit faad90a

Please sign in to comment.