Skip to content
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

feat!: add new content authoring event signals #21

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions docs/guides/hooks/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,55 @@ Content Authoring Events
- *Type*
- *Date added*

* - `COURSE_CATALOG_INFO_CHANGED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L23>`_
* - `COURSE_CATALOG_INFO_CHANGED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L25>`_
- org.openedx.content_authoring.course.catalog_info.changed.v1
- `2022-08-24 <https://github.com/openedx/edx-platform/blob/a8598fa1fac5e26ac212aa588e8527e727581742/cms/djangoapps/contentstore/signals/handlers.py#L111>`_

* - `XBLOCK_PUBLISHED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L30>`_
* - `XBLOCK_PUBLISHED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L63>`_
- org.openedx.content_authoring.xblock.published.v1
- `2022-12-06 <https://github.com/openedx/edx-platform/blob/master/xmodule/modulestore/mixed.py#L926>`_

* - `XBLOCK_DELETED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L42>`_
* - `XBLOCK_DELETED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L75>`_
- org.openedx.content_authoring.xblock.deleted.v1
- `2022-12-06 <https://github.com/openedx/edx-platform/blob/master/xmodule/modulestore/mixed.py#L804>`_

* - `XBLOCK_DUPLICATED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L54>`_
* - `XBLOCK_DUPLICATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L87>`_
- org.openedx.content_authoring.xblock.duplicated.v1
- `2022-12-06 <https://github.com/openedx/edx-platform/blob/master/cms/djangoapps/contentstore/views/item.py#L965>`_

* - `XBLOCK_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L36>`_
- org.openedx.content_authoring.xblock.created.v1
- 2023-07-20

* - `XBLOCK_UPDATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L47>`_
- org.openedx.content_authoring.xblock.updated.v1
- 2023-07-20

* - `COURSE_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L123>`_
- org.openedx.content_authoring.course.created.v1
- 2023-07-20

* - `CONTENT_LIBRARY_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L134>`_
- org.openedx.content_authoring.content_library.created.v1
- 2023-07-20

* - `CONTENT_LIBRARY_UPDATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L145>`_
- org.openedx.content_authoring.content_library.updated.v1
- 2023-07-20

* - `CONTENT_LIBRARY_DELETED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L156>`_
- org.openedx.content_authoring.content_library.deleted.v1
- 2023-07-20

* - `LIBRARY_BLOCK_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L167>`_
- org.openedx.content_authoring.content_library.created.v1
- 2023-07-20

* - `LIBRARY_BLOCK_UPDATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L178>`_
- org.openedx.content_authoring.content_library.updated.v1
- 2023-07-20

* - `LIBRARY_BLOCK_DELETED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L189>`_
- org.openedx.content_authoring.content_library.deleted.v1
- 2023-07-20

104 changes: 83 additions & 21 deletions openedx/core/djangoapps/content_libraries/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@
from lxml import etree
from opaque_keys.edx.keys import LearningContextKey, UsageKey
from opaque_keys.edx.locator import BundleDefinitionLocator, LibraryLocatorV2, LibraryUsageLocatorV2
from openedx_events.content_authoring.data import ContentLibraryData, LibraryBlockData
from openedx_events.content_authoring.signals import (
CONTENT_LIBRARY_CREATED,
CONTENT_LIBRARY_DELETED,
CONTENT_LIBRARY_UPDATED,
LIBRARY_BLOCK_CREATED,
LIBRARY_BLOCK_DELETED,
LIBRARY_BLOCK_UPDATED,
)

from organizations.models import Organization
from xblock.core import XBlock
from xblock.exceptions import XBlockNotFoundError
Expand All @@ -84,14 +94,6 @@
ContentLibraryPermission,
ContentLibraryBlockImportTask,
)
from openedx.core.djangoapps.content_libraries.signals import (
CONTENT_LIBRARY_CREATED,
CONTENT_LIBRARY_UPDATED,
CONTENT_LIBRARY_DELETED,
LIBRARY_BLOCK_CREATED,
LIBRARY_BLOCK_UPDATED,
LIBRARY_BLOCK_DELETED,
)
from openedx.core.djangoapps.olx_rest_api.block_serializer import XBlockSerializer
from openedx.core.djangoapps.xblock.api import get_block_display_name, load_block
from openedx.core.djangoapps.xblock.learning_context.manager import get_learning_context_impl
Expand Down Expand Up @@ -451,7 +453,11 @@ def create_library(
)
except IntegrityError:
raise LibraryAlreadyExists(slug) # lint-amnesty, pylint: disable=raise-missing-from
CONTENT_LIBRARY_CREATED.send(sender=None, library_key=ref.library_key)
CONTENT_LIBRARY_CREATED.send_event(
content_library=ContentLibraryData(
library_key=ref.library_key
)
)
return ContentLibraryMetadata(
key=ref.library_key,
bundle_uuid=bundle.uuid,
Expand Down Expand Up @@ -601,7 +607,11 @@ def update_library(
assert isinstance(description, str)
fields["description"] = description
update_bundle(ref.bundle_uuid, **fields)
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=ref.library_key)
CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData(
library_key=ref.library_key
)
)


def delete_library(library_key):
Expand All @@ -616,7 +626,11 @@ def delete_library(library_key):
# system, which is a better state than having a reference to a library with
# no backing blockstore bundle.
ref.delete()
CONTENT_LIBRARY_DELETED.send(sender=None, library_key=ref.library_key)
CONTENT_LIBRARY_DELETED.send_event(
content_library=ContentLibraryData(
library_key=ref.library_key
)
)
try:
delete_bundle(bundle_uuid)
except:
Expand Down Expand Up @@ -753,7 +767,12 @@ def set_library_block_olx(usage_key, new_olx_str):
write_draft_file(draft.uuid, metadata.def_key.olx_path, new_olx_str.encode('utf-8'))
# Clear the bundle cache so everyone sees the new block immediately:
BundleCache(metadata.def_key.bundle_uuid, draft_name=DRAFT_NAME).clear()
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=usage_key.context_key, usage_key=usage_key)
LIBRARY_BLOCK_UPDATED.send_event(
library_block=LibraryBlockData(
library_key=usage_key.context_key,
usage_key=usage_key
)
)


def create_library_block(library_key, block_type, definition_id):
Expand Down Expand Up @@ -802,7 +821,12 @@ def create_library_block(library_key, block_type, definition_id):
# Clear the bundle cache so everyone sees the new block immediately:
BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
# Now return the metadata about the new block:
LIBRARY_BLOCK_CREATED.send(sender=None, library_key=ref.library_key, usage_key=usage_key)
LIBRARY_BLOCK_CREATED.send_event(
library_block=LibraryBlockData(
library_key=ref.library_key,
usage_key=usage_key
)
)
return get_library_block(usage_key)


Expand Down Expand Up @@ -855,7 +879,12 @@ def delete_library_block(usage_key, remove_from_parent=True):
pass
# Clear the bundle cache so everyone sees the deleted block immediately:
lib_bundle.cache.clear()
LIBRARY_BLOCK_DELETED.send(sender=None, library_key=lib_bundle.library_key, usage_key=usage_key)
LIBRARY_BLOCK_DELETED.send_event(
library_block=LibraryBlockData(
library_key=lib_bundle.library_key,
usage_key=usage_key
)
)


def create_library_block_child(parent_usage_key, block_type, definition_id):
Expand All @@ -879,7 +908,12 @@ def create_library_block_child(parent_usage_key, block_type, definition_id):
parent_block.runtime.add_child_include(parent_block, include_data)
parent_block.save()
ref = ContentLibrary.objects.get_by_key(parent_usage_key.context_key)
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=ref.library_key, usage_key=metadata.usage_key)
LIBRARY_BLOCK_UPDATED.send_event(
library_block=LibraryBlockData(
library_key=ref.library_key,
usage_key=metadata.usage_key
)
)
return metadata


Expand Down Expand Up @@ -929,7 +963,12 @@ def add_library_block_static_asset_file(usage_key, file_name, file_content):
file_metadata = blockstore_cache.get_bundle_file_metadata_with_cache(
bundle_uuid=def_key.bundle_uuid, path=file_path, draft_name=DRAFT_NAME,
)
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=lib_bundle.library_key, usage_key=usage_key)
LIBRARY_BLOCK_UPDATED.send_event(
library_block=LibraryBlockData(
library_key=lib_bundle.library_key,
usage_key=usage_key
)
)
return LibraryXBlockStaticFile(path=file_metadata.path, url=file_metadata.url, size=file_metadata.size)


Expand All @@ -950,7 +989,12 @@ def delete_library_block_static_asset_file(usage_key, file_name):
write_draft_file(draft.uuid, file_path, contents=None)
# Clear the bundle cache so everyone sees the new file immediately:
lib_bundle.cache.clear()
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=lib_bundle.library_key, usage_key=usage_key)
LIBRARY_BLOCK_UPDATED.send_event(
library_block=LibraryBlockData(
library_key=lib_bundle.library_key,
usage_key=usage_key
)
)


def get_allowed_block_types(library_key): # pylint: disable=unused-argument
Expand Down Expand Up @@ -1043,7 +1087,11 @@ def create_bundle_link(library_key, link_id, target_opaque_key, version=None):
set_draft_link(draft.uuid, link_id, target_bundle_uuid, version)
# Clear the cache:
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key)
CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData(
library_key=library_key
)
)


def update_bundle_link(library_key, link_id, version=None, delete=False):
Expand All @@ -1067,7 +1115,11 @@ def update_bundle_link(library_key, link_id, version=None, delete=False):
set_draft_link(draft.uuid, link_id, link.bundle_uuid, version)
# Clear the cache:
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key)
CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData(
library_key=library_key
)
)


def publish_changes(library_key):
Expand All @@ -1083,7 +1135,12 @@ def publish_changes(library_key):
return # If there is no draft, no action is needed.
LibraryBundle(library_key, ref.bundle_uuid).cache.clear()
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key, update_blocks=True)
CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData(
library_key=library_key,
update_blocks=True
)
)


def revert_changes(library_key):
Expand All @@ -1099,7 +1156,12 @@ def revert_changes(library_key):
else:
return # If there is no draft, no action is needed.
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key, update_blocks=True)
CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData(
library_key=library_key,
update_blocks=True
)
)


# Import from Courseware
Expand Down
71 changes: 57 additions & 14 deletions openedx/core/djangoapps/content_libraries/libraries_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
from search.elastic import _translate_hits, RESERVED_CHARACTERS
from search.search_engine_base import SearchEngine
from opaque_keys.edx.locator import LibraryUsageLocatorV2

from openedx.core.djangoapps.content_libraries.constants import DRAFT_NAME
from openedx.core.djangoapps.content_libraries.signals import (
from openedx_events.content_authoring.data import ContentLibraryData, LibraryBlockData
from openedx_events.content_authoring.signals import (
CONTENT_LIBRARY_CREATED,
CONTENT_LIBRARY_UPDATED,
CONTENT_LIBRARY_DELETED,
CONTENT_LIBRARY_UPDATED,
LIBRARY_BLOCK_CREATED,
LIBRARY_BLOCK_UPDATED,
LIBRARY_BLOCK_DELETED,
LIBRARY_BLOCK_UPDATED,
)

from openedx.core.djangoapps.content_libraries.constants import DRAFT_NAME
from openedx.core.djangoapps.content_libraries.library_bundle import LibraryBundle
from openedx.core.djangoapps.content_libraries.models import ContentLibrary
from openedx.core.lib.blockstore_api import get_bundle
Expand Down Expand Up @@ -242,17 +243,21 @@ def get_item_definition(cls, item):

@receiver(CONTENT_LIBRARY_CREATED)
@receiver(CONTENT_LIBRARY_UPDATED)
@receiver(LIBRARY_BLOCK_CREATED)
@receiver(LIBRARY_BLOCK_UPDATED)
@receiver(LIBRARY_BLOCK_DELETED)
def index_library(sender, library_key, **kwargs): # pylint: disable=unused-argument
def index_library(**kwargs):
"""
Index library when created or updated, or when its blocks are modified.
"""
content_library = kwargs.get('content_library', None)
if not content_library or not isinstance(content_library, ContentLibraryData):
log.error('Received null or incorrect data for event')
return

library_key = content_library.library_key
update_blocks = content_library.update_blocks
if ContentLibraryIndexer.indexing_is_enabled():
try:
ContentLibraryIndexer.index_items([library_key])
if kwargs.get('update_blocks', False):
if update_blocks:
blocks = LibraryBlockIndexer.get_items(filter_terms={
'library_key': str(library_key)
})
Expand All @@ -262,12 +267,38 @@ def index_library(sender, library_key, **kwargs): # pylint: disable=unused-argu
log.exception(e)


@receiver(LIBRARY_BLOCK_CREATED)
@receiver(LIBRARY_BLOCK_DELETED)
@receiver(LIBRARY_BLOCK_UPDATED)
def index_library_block(**kwargs):
"""
Index library when its blocks are created, modified, or deleted.
"""
library_block = kwargs.get('library_block', None)
if not library_block or not isinstance(library_block, LibraryBlockData):
log.error('Received null or incorrect data for event')
return

library_key = library_block.library_key
if ContentLibraryIndexer.indexing_is_enabled():
try:
ContentLibraryIndexer.index_items([library_key])
except ElasticConnectionError as e:
log.exception(e)


@receiver(CONTENT_LIBRARY_DELETED)
def remove_library_index(sender, library_key, **kwargs): # pylint: disable=unused-argument
def remove_library_index(**kwargs):
"""
Remove from index when library is deleted
"""
content_library = kwargs.get('content_library', None)
if not content_library or not isinstance(content_library, ContentLibraryData):
log.error('Received null or incorrect data for event')
return

if ContentLibraryIndexer.indexing_is_enabled():
library_key = content_library.library_key
try:
ContentLibraryIndexer.remove_items([library_key])
blocks = LibraryBlockIndexer.get_items(filter_terms={
Expand All @@ -280,10 +311,16 @@ def remove_library_index(sender, library_key, **kwargs): # pylint: disable=unus

@receiver(LIBRARY_BLOCK_CREATED)
@receiver(LIBRARY_BLOCK_UPDATED)
def index_block(sender, usage_key, **kwargs): # pylint: disable=unused-argument
def index_block(**kwargs):
"""
Index block metadata when created
Index block metadata when created or updated
"""
library_block = kwargs.get('library_block', None)
if not library_block or not isinstance(library_block, LibraryBlockData):
log.error('Received null or incorrect data for event')
return

usage_key = library_block.usage_key
if LibraryBlockIndexer.indexing_is_enabled():
try:
LibraryBlockIndexer.index_items([usage_key])
Expand All @@ -292,10 +329,16 @@ def index_block(sender, usage_key, **kwargs): # pylint: disable=unused-argument


@receiver(LIBRARY_BLOCK_DELETED)
def remove_block_index(sender, usage_key, **kwargs): # pylint: disable=unused-argument
def remove_block_index(**kwargs):
"""
Remove the block from the index when deleted
"""
library_block = kwargs.get('library_block', None)
if not library_block or not isinstance(library_block, LibraryBlockData):
log.error('Received null or incorrect data for LIBRARY_BLOCK_DELETED')
return

usage_key = library_block.usage_key
if LibraryBlockIndexer.indexing_is_enabled():
try:
LibraryBlockIndexer.remove_items([usage_key])
Expand Down
Loading
Loading