Skip to content

Commit

Permalink
fix: Workflow update to integrate the waffle flag course and be able …
Browse files Browse the repository at this point in the history
…to send to edx-submission

test: Adjust styles

feat: course unit - edit iframe modal window (openedx#35777)

Adds logic to support the functionality of editing xblocks via the legacy modal editing window.

docs: fixing make docs command. (openedx#36280)

* docs: fixing make docs command.

fix: import authoring filter from content_authoring instead (openedx#36109)

Correctly name authoring subdomain according to DDD docs: https://openedx.atlassian.net/wiki/spaces/AC/pages/663224968/edX+DDD+Bounded+Contexts

fix: ADR document update for change from Xqueue to edx-submission
  • Loading branch information
gabrielC1409 committed Feb 21, 2025
1 parent 22687d6 commit d677768
Show file tree
Hide file tree
Showing 33 changed files with 2,610 additions and 279 deletions.
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/asset_storage_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from common.djangoapps.util.json_request import JsonResponse
from openedx.core.djangoapps.contentserver.caching import del_cached_content
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx_filters.course_authoring.filters import LMSPageURLRequested
from openedx_filters.content_authoring.filters import LMSPageURLRequested
from xmodule.contentstore.content import StaticContent # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.exceptions import NotFoundError # lint-amnesty, pylint: disable=wrong-import-order
Expand Down Expand Up @@ -717,7 +717,7 @@ def get_asset_json(display_name, content_type, date, location, thumbnail_locatio
asset_url = StaticContent.serialize_asset_key_with_slash(location)

## .. filter_implemented_name: LMSPageURLRequested
## .. filter_type: org.openedx.course_authoring.lms.page.url.requested.v1
## .. filter_type: org.openedx.content_authoring.lms.page.url.requested.v1
lms_root, _ = LMSPageURLRequested.run_filter(
url=configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL),
org=location.org,
Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def setUp(self): # pylint: disable=arguments-differ

@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.course_authoring.lms.page.url.requested.v1": {
"org.openedx.content_authoring.lms.page.url.requested.v1": {
"pipeline": [
"common.djangoapps.util.tests.test_filters.TestPageURLRequestedPipelineStep",
],
Expand Down
38 changes: 37 additions & 1 deletion cms/djangoapps/contentstore/views/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
from django.db import transaction
from django.http import Http404, HttpResponse
from django.utils.translation import gettext as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.http import require_http_methods
from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment

from cms.djangoapps.contentstore.utils import load_services_for_studio
from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW
from common.djangoapps.edxmako.shortcuts import render_to_string
from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string
from common.djangoapps.student.auth import (
has_studio_read_access,
has_studio_write_access,
Expand Down Expand Up @@ -44,6 +45,8 @@
is_unit,
)
from .preview import get_preview_fragment
from .component import _get_item_in_course
from ..utils import get_container_handler_context

from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import (
handle_xblock,
Expand Down Expand Up @@ -302,6 +305,39 @@ def xblock_view_handler(request, usage_key_string, view_name):
return HttpResponse(status=406)


@xframe_options_exempt
@require_http_methods(["GET"])
@login_required
def xblock_edit_view(request, usage_key_string):
"""
Return rendered xblock edit view.
Allows editing of an XBlock specified by the usage key.
"""
usage_key = usage_key_with_run(usage_key_string)
if not has_studio_read_access(request.user, usage_key.course_key):
raise PermissionDenied()

store = modulestore()

with store.bulk_operations(usage_key.course_key):
course, xblock, _, __ = _get_item_in_course(request, usage_key)
container_handler_context = get_container_handler_context(request, usage_key, course, xblock)

fragment = get_preview_fragment(request, xblock, {})

hashed_resources = {
hash_resource(resource): resource._asdict() for resource in fragment.resources
}

container_handler_context.update({
"action_name": "edit",
"resources": list(hashed_resources.items()),
})

return render_to_response('container_editor.html', container_handler_context)


@require_http_methods("GET")
@login_required
@expect_json
Expand Down
59 changes: 59 additions & 0 deletions cms/djangoapps/contentstore/views/tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from pyquery import PyQuery
from pytz import UTC
from bs4 import BeautifulSoup
from web_fragments.fragment import Fragment
from webob import Response
from xblock.core import XBlockAside
Expand Down Expand Up @@ -4538,3 +4539,61 @@ def test_update_clobbers(self):
user_id=user.id,
)
self.check_updated(source_block, destination_block.location)


class TestXblockEditView(CourseTestCase):
"""
Test xblock_edit_view.
"""

def setUp(self):
super().setUp()
self.chapter = self._create_block(self.course, "chapter", "Week 1")
self.sequential = self._create_block(self.chapter, "sequential", "Lesson 1")
self.vertical = self._create_block(self.sequential, "vertical", "Unit")
self.html = self._create_block(self.vertical, "html", "HTML")
self.child_container = self._create_block(
self.vertical, "split_test", "Split Test"
)
self.child_vertical = self._create_block(
self.child_container, "vertical", "Child Vertical"
)
self.video = self._create_block(self.child_vertical, "video", "My Video")
self.store = modulestore()

self.store.publish(self.vertical.location, self.user.id)

def _create_block(self, parent, category, display_name, **kwargs):
"""
creates a block in the module store, without publishing it.
"""
return BlockFactory.create(
parent=parent,
category=category,
display_name=display_name,
publish_item=False,
user_id=self.user.id,
**kwargs,
)

def test_xblock_edit_view(self):
url = reverse_usage_url("xblock_edit_handler", self.video.location)
resp = self.client.get_html(url)
self.assertEqual(resp.status_code, 200)

html_content = resp.content.decode(resp.charset)
self.assertIn("var decodedActionName = 'edit';", html_content)

def test_xblock_edit_view_contains_resources(self):
url = reverse_usage_url("xblock_edit_handler", self.video.location)
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)

html_content = resp.content.decode(resp.charset)
soup = BeautifulSoup(html_content, "html.parser")

resource_links = [link["href"] for link in soup.find_all("link", {"rel": "stylesheet"})]
script_sources = [script["src"] for script in soup.find_all("script") if script.get("src")]

self.assertGreater(len(resource_links), 0, f"No CSS resources found in HTML. Found: {resource_links}")
self.assertGreater(len(script_sources), 0, f"No JS resources found in HTML. Found: {script_sources}")
11 changes: 11 additions & 0 deletions cms/static/cms/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ define([
title: gettext("Studio's having trouble saving your work"),
message: message
});
if (window.self !== window.top) {
try {
window.parent.postMessage({
type: 'studioAjaxError',
message: 'Sends a message when an AJAX error occurs',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}
}
console.log('Studio AJAX Error', { // eslint-disable-line no-console
url: event.currentTarget.URL,
response: jqXHR.responseText,
Expand Down
22 changes: 11 additions & 11 deletions cms/static/js/views/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,6 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView)
newParent = undefined;
},
update: function(event, ui) {
try {
window.parent.postMessage(
{
type: 'refreshPositions',
message: 'Refresh positions of all xblocks',
payload: {}
}, document.referrer
);
} catch (e) {
console.error(e);
}
// When dragging from one ol to another, this method
// will be called twice (once for each list). ui.sender will
// be null if the change is related to the list the element
Expand Down Expand Up @@ -137,6 +126,17 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView)
if (successCallback) {
successCallback();
}
try {
window.parent.postMessage(
{
type: 'refreshPositions',
message: 'Refresh positions of all xblocks',
payload: {}
}, document.referrer
);
} catch (e) {
console.error(e);
}
// Update publish and last modified information from the server.
xblockInfo.fetch();
}
Expand Down
21 changes: 21 additions & 0 deletions cms/static/js/views/modals/base_modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
},

hide: function() {
try {
window.parent.postMessage(
{
type: 'hideXBlockEditorModal',
message: 'Sends a message when the modal window is hided',
payload: {}
}, document.referrer
);
} catch (e) {
console.error(e);
}

// Completely remove the modal from the DOM
this.undelegateEvents();
this.$el.html('');
Expand All @@ -119,6 +131,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
event.preventDefault();
event.stopPropagation(); // Make sure parent modals don't see the click
}
try {
window.parent.postMessage({
type: 'closeXBlockEditorModal',
message: 'Sends a message when the modal window is closed',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}
this.hide();
},

Expand Down
25 changes: 25 additions & 0 deletions cms/static/js/views/modals/edit_xblock.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
},

getXBlockUpstreamLink: function() {
if (!this.xblockElement || !this.xblockElement.length) {
console.error('xblockElement is empty or not defined');
return;
}

const usageKey = this.xblockElement.data('locator');
$.ajax({
url: '/api/contentstore/v2/downstreams/' + usageKey,
Expand Down Expand Up @@ -219,6 +224,16 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
},

onSave: function() {
try {
window.parent.postMessage({
type: 'saveEditedXBlockData',
message: 'Sends a message when the xblock data is saved',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}

var refresh = this.editOptions.refresh;
this.hide();
if (refresh) {
Expand All @@ -230,6 +245,16 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
// Notify child views to stop listening events
Backbone.trigger('xblock:editorModalHidden');

try {
window.parent.postMessage({
type: 'closeXBlockEditorModal',
message: 'Sends a message when the modal window is closed',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}

BaseModal.prototype.hide.call(this);

// Notify the runtime that the modal has been hidden
Expand Down
44 changes: 28 additions & 16 deletions cms/static/js/views/pages/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ function($, _, Backbone, gettext, BasePage,
case 'refreshXBlock':
this.render();
break;
case 'completeXBlockEditing':
this.refreshXBlock(xblockElement, false);
break;
case 'completeManageXBlockAccess':
this.refreshXBlock(xblockElement, false);
break;
Expand Down Expand Up @@ -507,6 +510,18 @@ function($, _, Backbone, gettext, BasePage,
window.location.href = destinationUrl;
return;
}

if (this.options.isIframeEmbed) {
return window.parent.postMessage(
{
type: 'editXBlock',
message: 'Sends a message when the legacy modal window is shown',
payload: {
id: this.findXBlockElement(event.target).data('locator')
}
}, document.referrer
);
}
}

var xblockElement = this.findXBlockElement(event.target),
Expand Down Expand Up @@ -1050,23 +1065,20 @@ function($, _, Backbone, gettext, BasePage,
},

viewXBlockContent: function(event) {
try {
if (this.options.isIframeEmbed) {
event.preventDefault();
var usageId = event.currentTarget.href.split('/').pop() || '';
window.parent.postMessage(
{
type: 'handleViewXBlockContent',
payload: {
usageId: usageId,
},
}, document.referrer
);
return true;
try {
if (this.options.isIframeEmbed) {
event.preventDefault();
var usageId = event.currentTarget.href.split('/').pop() || '';
window.parent.postMessage({
type: 'handleViewXBlockContent',
message: 'View the content of the XBlock',
payload: { usageId },
}, document.referrer);
return true;
}
} catch (e) {
console.error(e);
}
} catch (e) {
console.error(e);
}
},

toggleSaveButton: function() {
Expand Down
4 changes: 3 additions & 1 deletion cms/static/js/views/pages/container_subviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,9 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
tagValueElement.className = 'tagging-label-value';

tagContentElement.appendChild(tagValueElement);
parentElement.appendChild(tagContentElement);
if (parentElement) {
parentElement.appendChild(tagContentElement);
}

if (tag.children.length > 0) {
var tagIconElement = document.createElement('span'),
Expand Down
Loading

0 comments on commit d677768

Please sign in to comment.