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: endpoint to publish single library component #35677

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
25 changes: 25 additions & 0 deletions openedx/core/djangoapps/content_libraries/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,31 @@ def publish_changes(library_key, user_id=None):
)


def publish_component_changes(usage_key: LibraryUsageLocatorV2, user):
"""
Publish all pending changes in a single component.
"""
content_library = require_permission_for_library_key(
usage_key.lib_key,
user,
permissions.CAN_EDIT_THIS_CONTENT_LIBRARY
)
learning_package = content_library.learning_package

assert learning_package
component = get_component_from_usage_key(usage_key)
drafts_to_publish = authoring_api.get_all_drafts(learning_package.id).filter(
entity__key=component.key
)
authoring_api.publish_from_drafts(learning_package.id, draft_qset=drafts_to_publish, published_by=user.id)
LIBRARY_BLOCK_UPDATED.send_event(
library_block=LibraryBlockData(
library_key=usage_key.lib_key,
usage_key=usage_key,
)
)


def revert_changes(library_key):
"""
Revert all pending changes to the specified library, restoring it to the
Expand Down
5 changes: 5 additions & 0 deletions openedx/core/djangoapps/content_libraries/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
URL_LIB_TEAM_GROUP = URL_LIB_TEAM + 'group/{group_name}/' # Add/edit/remove a group's permission to use this library
URL_LIB_PASTE_CLIPBOARD = URL_LIB_DETAIL + 'paste_clipboard/' # Paste user clipboard (POST) containing Xblock data
URL_LIB_BLOCK = URL_PREFIX + 'blocks/{block_key}/' # Get data about a block, or delete it
URL_LIB_BLOCK_PUBLISH = URL_LIB_BLOCK + 'publish/' # Publish changes from a specified XBlock
URL_LIB_BLOCK_OLX = URL_LIB_BLOCK + 'olx/' # Get or set the OLX of the specified XBlock
URL_LIB_BLOCK_ASSETS = URL_LIB_BLOCK + 'assets/' # List the static asset files of the specified XBlock
URL_LIB_BLOCK_ASSET_FILE = URL_LIB_BLOCK + 'assets/{file_name}' # Get, delete, or upload a specific static asset file
Expand Down Expand Up @@ -286,6 +287,10 @@ def _delete_library_block_asset(self, block_key, file_name, expect_response=204)
url = URL_LIB_BLOCK_ASSET_FILE.format(block_key=block_key, file_name=file_name)
return self._api('delete', url, None, expect_response)

def _publish_library_block(self, block_key, expect_response=200):
""" Publish changes from a specified XBlock """
return self._api('post', URL_LIB_BLOCK_PUBLISH.format(block_key=block_key), None, expect_response)

def _paste_clipboard_content_in_library(self, lib_key, block_id, expect_response=200):
""" Paste's the users clipboard content into Library """
url = URL_LIB_PASTE_CLIPBOARD.format(lib_key=lib_key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def test_library_filters(self):

# General Content Library XBlock tests:

def test_library_blocks(self):
def test_library_blocks(self): # pylint: disable=too-many-statements
"""
Test the happy path of creating and working with XBlocks in a content
library.
Expand Down Expand Up @@ -359,6 +359,21 @@ def test_library_blocks(self):
assert self._get_library(lib_id)['has_unpublished_deletes'] is False
assert self._get_library_block_olx(block_id) == orig_olx

# Now edit and publish the single block instead of the whole library:
new_olx = "<problem><p>Edited OLX</p></problem>"
self._set_library_block_olx(block_id, new_olx)
assert self._get_library_block_olx(block_id) == new_olx
unpublished_block_data = self._get_library_block(block_id)
assert unpublished_block_data['has_unpublished_changes'] is True
block_update_date = datetime(2024, 8, 8, 8, 8, 9, tzinfo=timezone.utc)
with freeze_time(block_update_date):
self._publish_library_block(block_id)
# Confirm the block is now published:
published_block_data = self._get_library_block(block_id)
assert published_block_data['last_published'] == block_update_date.isoformat().replace('+00:00', 'Z')
assert published_block_data['published_by'] == "Bob"
assert published_block_data['has_unpublished_changes'] is False

# fin

def test_library_blocks_studio_view(self):
Expand Down Expand Up @@ -675,12 +690,13 @@ def test_library_permissions(self): # pylint: disable=too-many-statements
# self._get_library_block_assets(block3_key)
# self._get_library_block_asset(block3_key, file_name="whatever.png")

# Users without authoring permission cannot edit nor delete XBlocks:
# Users without authoring permission cannot edit nor publish nor delete XBlocks:
for user in [reader, random_user]:
with self.as_user(user):
self._set_library_block_olx(block3_key, "<problem/>", expect_response=403)
self._set_library_block_fields(block3_key, {"data": "<problem />", "metadata": {}}, expect_response=403)
self._set_library_block_asset(block3_key, "static/test.txt", b"data", expect_response=403)
self._publish_library_block(block3_key, expect_response=403)
self._delete_library_block(block3_key, expect_response=403)
self._commit_library_changes(lib_id, expect_response=403)
self._revert_library_changes(lib_id, expect_response=403)
Expand All @@ -694,9 +710,20 @@ def test_library_permissions(self): # pylint: disable=too-many-statements
self._set_library_block_asset(block3_key, "static/test.txt", b"data")
self._get_library_block_asset(block3_key, file_name="static/test.txt")
self._delete_library_block(block3_key)
self._publish_library_block(block3_key)
self._commit_library_changes(lib_id)
self._revert_library_changes(lib_id) # This is a no-op after the commit, but should still have 200 response

# Users without authoring permission cannot commit Xblock changes:
# First we need to add some unpublished changes
with self.as_user(admin):
block4_data = self._add_block_to_library(lib_id, "problem", "problem4")
block5_data = self._add_block_to_library(lib_id, "problem", "problem5")
block4_key = block4_data["id"]
block5_key = block5_data["id"]
self._set_library_block_olx(block4_key, "<problem/>")
self._set_library_block_olx(block5_key, "<problem/>")

def test_no_lockout(self):
"""
Test that administrators cannot be removed if they are the only administrator granted access.
Expand Down
3 changes: 2 additions & 1 deletion openedx/core/djangoapps/content_libraries/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
# CRUD for static asset files associated with a block in the library:
path('assets/', views.LibraryBlockAssetListView.as_view()),
path('assets/<path:file_path>', views.LibraryBlockAssetView.as_view()),
# Future: publish/discard changes for just this one block
path('publish/', views.LibraryBlockPublishView.as_view()),
# Future: discard changes for just this one block
# Future: set a block's tags (tags are stored in a Tag bundle and linked in)
])),
re_path(r'^lti/1.3/', include([
Expand Down
14 changes: 14 additions & 0 deletions openedx/core/djangoapps/content_libraries/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,20 @@ def delete(self, request, usage_key_str, file_path):
return Response(status=status.HTTP_204_NO_CONTENT)


@method_decorator(non_atomic_requests, name="dispatch")
@view_auth_classes()
class LibraryBlockPublishView(APIView):
"""
Commit/publish all of the draft changes made to the component.
"""

@convert_exceptions
def post(self, request, usage_key_str):
key = LibraryUsageLocatorV2.from_string(usage_key_str)
api.publish_component_changes(key, request.user)
return Response({})
Copy link
Contributor

Choose a reason for hiding this comment

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

In the future, we might make this return the updated metadata for the block.

We could also add a "revert single component" API by sending a DELETE request to this same endpoint.

This is good for now though :)



@method_decorator(non_atomic_requests, name="dispatch")
@view_auth_classes()
class LibraryImportTaskViewSet(GenericViewSet):
Expand Down
2 changes: 1 addition & 1 deletion requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ optimizely-sdk<5.0
# Date: 2023-09-18
# pinning this version to avoid updates while the library is being developed
# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269
openedx-learning==0.16.0
openedx-learning==0.16.1

# Date: 2023-11-29
# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ openedx-filters==1.11.0
# -r requirements/edx/kernel.in
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,7 @@ openedx-filters==1.11.0
# -r requirements/edx/testing.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ openedx-filters==1.11.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ openedx-filters==1.11.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
Expand Down
Loading