feat: adds VerticalBlockRenderCompleted filter hook (#31388)

* feat: adds VerticalBlockChildrenLoaded filter call

This introduces the VerticalBlockChildrenLoaded filter that is run after
all the child blocks are fetched before rendering a student or the
public view. This will allow modifying the contents of the VerticalBlock
before presenting it to the students.
This commit is contained in:
Arunmozhi
2023-02-23 00:35:19 +05:30
committed by GitHub
parent cf1eb18949
commit 2b59265240
3 changed files with 176 additions and 10 deletions

View File

@@ -182,6 +182,10 @@ well as the trigger location in this same repository.
- org.openedx.learning.dashboard.render.started.v1
- `2022-06-14 <https://github.com/eduNEXT/edx-platform/blob/master/common/djangoapps/student/views/dashboard.py#L878>`_
* - `VerticalChildRenderStarted <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L427>`_
- org.openedx.learning.veritical_child_block.render.started.v1
- `2022-08-18 <https://github.com/openedx/edx-platform/blob/master/xmodule/vertical_block.py#L122>`_
* - `VerticalBlockChildRenderStarted <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L427>`_
- org.openedx.learning.veritical_block_child.render.started.v1
- `2022-08-18 <https://github.com/openedx/edx-platform/blob/master/xmodule/vertical_block.py#L170>`_
* - `VerticalBlockRenderCompleted <https://github.com/openedx/openedx-filters/blob/main/openedx_filters/learning/filters.py#L476>`_
- org.openedx.learning.veritical_block.render.completed.v1
- `2022-02-18 <https://github.com/openedx/edx-platform/blob/master/xmodule/vertical_block.py#L121>`_

View File

@@ -14,6 +14,9 @@ import pytz
import ddt
from fs.memoryfs import MemoryFS
from django.contrib.auth.models import AnonymousUser
from django.test import override_settings
from openedx_filters import PipelineStep
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted
from . import get_test_system
from .helpers import StubUserService
@@ -78,6 +81,62 @@ class StubCompletionService:
return self._completion_value == 1 if self._enabled else None
class TestVerticalBlockChildRenderStep(PipelineStep):
"""
Utility class for testing filters on vertical block children
"""
filter_content = "Altered Content"
def run_filter(self, block, context): # lint-amnesty, pylint: disable=arguments-differ
"""Pipeline step that changes child content"""
if type(block).__name__ == "HtmlBlockWithMixins":
block.get_html = lambda: TestVerticalBlockChildRenderStep.filter_content
return {"block": block, "context": context}
class TestPreventVerticalBlockChildRender(PipelineStep):
"""
Utility class to test vertical block children are skipped in rendering.
"""
def run_filter(self, block, context): # lint-amnesty, pylint: disable=arguments-differ
"""Pipeline step that raises exceptions during child block rendering"""
if type(block).__name__ == "HtmlBlockWithMixins":
raise VerticalBlockChildRenderStarted.PreventChildBlockRender(
"Skip block test exception"
)
class TestVerticalBlockRenderCompletedStep(PipelineStep):
"""
Utility class for testing filters on vertical block render completion
"""
filter_content = "Extra content added"
def run_filter(self, block, fragment, context, view): # lint-amnesty, pylint: disable=arguments-differ
"""Pipeline step that alters the output of the fragment"""
fragment.content += TestVerticalBlockRenderCompletedStep.filter_content
return {
"block": block,
"fragment": fragment,
"context": context,
"view": view
}
class TestPreventVerticalBlockRenderStep(PipelineStep):
"""
Utility class for testing VerticalBlock render can be stopped.
"""
filter_content = "<div class=\"alert alert-danger\">Assignments are not available for Audit students.<div>"
def run_filter(self, block, fragment, context, view): # lint-amnesty, pylint: disable=arguments-differ
"""Pipeline step that raises an exception"""
raise VerticalBlockRenderCompleted.PreventVerticalBlockRender(
TestPreventVerticalBlockRenderStep.filter_content
)
class BaseVerticalBlockTest(XModuleXmlImportTest):
"""
Tests for the BaseVerticalBlock.
@@ -289,3 +348,92 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content
assert self.test_html in html
assert self.test_problem in html
@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.vertical_block_child.render.started.v1": {
"pipeline": [
"xmodule.tests.test_vertical.TestVerticalBlockChildRenderStep"
],
"fail_silently": False,
},
},
)
def test_vertical_block_child_render_started_filter_execution(self):
"""
Test the VerticalBlockChildRenderStarted filter's effects on student view.
"""
self.module_system._services['bookmarks'] = Mock()
self.module_system._services['user'] = StubUserService(user=Mock())
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
assert TestVerticalBlockChildRenderStep.filter_content in html
@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.vertical_block_child.render.started.v1": {
"pipeline": [
"xmodule.tests.test_vertical.TestPreventVerticalBlockChildRender"
],
"fail_silently": False,
},
},
)
def test_vertical_block_child_render_is_skipped_on_filter_exception(self):
"""
Test VerticalBlockChildRenderStarted filter can be used to skip child blocks.
"""
self.module_system._services['bookmarks'] = Mock()
self.module_system._services['user'] = StubUserService(user=Mock())
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
assert self.test_html not in html
assert self.test_html_nested not in html
@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.vertical_block.render.completed.v1": {
"pipeline": [
"xmodule.tests.test_vertical.TestVerticalBlockRenderCompletedStep"
],
"fail_silently": False,
},
},
)
def test_vertical_block_render_completed_filter_execution(self):
"""
Test the VerticalBlockRenderCompleted filter's execution.
"""
self.module_system._services['bookmarks'] = Mock()
self.module_system._services['user'] = StubUserService(user=Mock())
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
assert TestVerticalBlockRenderCompletedStep.filter_content in html
@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.vertical_block.render.completed.v1": {
"pipeline": [
"xmodule.tests.test_vertical.TestPreventVerticalBlockRenderStep"
],
"fail_silently": False,
},
},
)
def test_vertical_block_render_output_is_changed_on_filter_exception(self):
"""
Test VerticalBlockRenderCompleted filter can be used to prevent vertical block from rendering.
"""
self.module_system._services['bookmarks'] = Mock()
self.module_system._services['user'] = StubUserService(user=Mock())
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
assert TestPreventVerticalBlockRenderStep.filter_content == html

View File

@@ -13,7 +13,7 @@ from lxml import etree
from web_fragments.fragment import Fragment
from xblock.core import XBlock # lint-amnesty, pylint: disable=wrong-import-order
from xblock.fields import Boolean, Scope
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted
from xmodule.mako_block import MakoTemplateBlockBase
from xmodule.progress import Progress
from xmodule.seq_block import SequenceFields
@@ -72,7 +72,7 @@ class VerticalBlock(
show_in_read_only_mode = True
def _student_or_public_view(self, context, view):
def _student_or_public_view(self, context, view): # lint-amnesty, pylint: disable=too-many-statements
"""
Renders the requested view type of the block in the LMS.
"""
@@ -117,11 +117,15 @@ class VerticalBlock(
'mark-completed-on-view-after-delay': complete_on_view_delay
}
# .. filter_implemented_name: VerticalBlockChildRenderStarted
# .. filter_type: org.openedx.learning.vertical_block_child.render.started.v1
child, child_block_context = VerticalBlockChildRenderStarted.run_filter(
block=child, context=child_block_context
)
try:
# .. filter_implemented_name: VerticalBlockChildRenderStarted
# .. filter_type: org.openedx.learning.vertical_block_child.render.started.v1
child, child_block_context = VerticalBlockChildRenderStarted.run_filter(
block=child, context=child_block_context
)
except VerticalBlockChildRenderStarted.PreventChildBlockRender as exc:
log.info("Skipping %s from vertical block. Reason: %s", child, exc.message)
continue
rendered_child = child.render(view, child_block_context)
fragment.add_fragment_resources(rendered_child)
@@ -162,6 +166,16 @@ class VerticalBlock(
add_webpack_to_fragment(fragment, 'VerticalStudentView')
fragment.initialize_js('VerticalStudentView')
try:
# .. filter_implemented_name: VerticalBlockRenderCompleted
# .. filter_type: org.openedx.learning.vertical_block.render.completed.v1
_, fragment, context, view = VerticalBlockRenderCompleted.run_filter(
block=self, fragment=fragment, context=context, view=view
)
except VerticalBlockRenderCompleted.PreventVerticalBlockRender as exc:
log.info("VerticalBlock rendering stopped. Reason: %s", exc.message)
fragment.content = exc.message
return fragment
def block_has_access_error(self, block):