ol_openedx_feedback: Per-block learner feedback in learning MFE#813
Conversation
69914af to
9ec8f0c
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new ol_openedx_feedback Open edX plugin that registers an XBlockAside to render a per-block “Send feedback” trigger in LMS student view and forward block context to the Learning MFE via postMessage, gated by a course waffle flag.
Changes:
- Introduces the
FeedbackAsideXBlockAside (plus waffle-flag compat helper) to gate and render a lightweight trigger-only UI on leaf blocks. - Adds frontend assets (HTML/CSS/JS) for the trigger and its interaction/placement behavior alongside AskTIM when present.
- Adds pytest coverage and shared test utilities for applicability gating and student-view rendering behavior.
Reviewed changes
Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/ol_openedx_feedback/pyproject.toml | New plugin package metadata + entry points for the aside and Django app registration. |
| src/ol_openedx_feedback/README.rst | Documents trigger behavior, message contract, and waffle-flag enablement. |
| src/ol_openedx_feedback/setup.cfg | Plugin-local pytest/isort configuration mirroring other plugins. |
| src/ol_openedx_feedback/ol_openedx_feedback/init.py | Initializes the Python package. |
| src/ol_openedx_feedback/ol_openedx_feedback/apps.py | Declares an aside-only Django AppConfig (plugin_app = {}). |
| src/ol_openedx_feedback/ol_openedx_feedback/constants.py | Defines container block types excluded from applicability. |
| src/ol_openedx_feedback/ol_openedx_feedback/utils.py | Adds helper to determine whether a block is eligible (leaf vs container). |
| src/ol_openedx_feedback/ol_openedx_feedback/compat.py | Encapsulates CourseWaffleFlag wiring for ol_openedx_feedback.feedback_enabled. |
| src/ol_openedx_feedback/ol_openedx_feedback/block.py | Implements the FeedbackAside rendering + waffle-gated should_apply_to_block. |
| src/ol_openedx_feedback/ol_openedx_feedback/static/html/student_view.html | Trigger markup (button + inline icon) with accessibility attributes. |
| src/ol_openedx_feedback/ol_openedx_feedback/static/css/feedback.css | Styling for trigger placement and focus/hover states. |
| src/ol_openedx_feedback/ol_openedx_feedback/static/js/feedback.js | JS to emit ol-feedback::drawer-open and optionally dock beside AskTIM. |
| src/ol_openedx_feedback/tests/init.py | Marks tests as a package. |
| src/ol_openedx_feedback/tests/conftest.py | Adds logging-control pytest CLI options (consistent with other plugins). |
| src/ol_openedx_feedback/tests/utils.py | Shared ModuleStore-backed test base that builds a course tree + aside instances. |
| src/ol_openedx_feedback/tests/test_utils.py | Unit tests for leaf/container applicability helper. |
| src/ol_openedx_feedback/tests/test_aside.py | Tests for rendering gates and should_apply_to_block waffle/import behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
f922b23 to
5fc18ff
Compare
asadali145
left a comment
There was a problem hiding this comment.
We should add the plugin metadata in AGENTS.md. Also, I have a few suggestions.
| if getattr(self.runtime, "is_author_mode", False): | ||
| return fragment | ||
| if not getattr(self.runtime, "user_id", None): | ||
| return fragment |
There was a problem hiding this comment.
| if getattr(self.runtime, "is_author_mode", False): | |
| return fragment | |
| if not getattr(self.runtime, "user_id", None): | |
| return fragment | |
| if getattr(self.runtime, "is_author_mode", False) or not getattr(self.runtime, "user_id", None): | |
| return fragment |
| if isinstance(block.runtime, XMLImportingModuleStoreRuntime): | ||
| return is_aside_applicable_to_block(block) |
There was a problem hiding this comment.
We don't need this check in this Aside, it was required for ol_openedx_chat because of the checks it applied in is_aside_applicable_to_block.
| def test_leaf_blocks_are_applicable(): | ||
| assert is_aside_applicable_to_block(_block("video")) is True | ||
| assert is_aside_applicable_to_block(_block("problem")) is True | ||
| assert is_aside_applicable_to_block(_block("html")) is True | ||
|
|
||
|
|
||
| def test_container_blocks_are_excluded(): | ||
| assert is_aside_applicable_to_block(_block("vertical")) is False | ||
| assert is_aside_applicable_to_block(_block("sequential")) is False | ||
| assert is_aside_applicable_to_block(_block("chapter")) is False | ||
| assert is_aside_applicable_to_block(_block("course")) is False | ||
|
|
||
|
|
||
| def test_missing_category_is_not_applicable(): | ||
| assert is_aside_applicable_to_block(_block(None)) is False |
There was a problem hiding this comment.
Can be a single parametrized test with the block name and expected output.
| return Template(_resource(path)).render(Context(context)) | ||
|
|
||
|
|
||
| class FeedbackAside(XBlockAside): |
There was a problem hiding this comment.
We can't disable feedback for a block from the authoring MFE, and also, we cannot disable it for a block type like text block, etc.
I think we should add the ability to disable feedback for a block from authoring and convert the constant to a setting to be able to disable it for a block type.
There was a problem hiding this comment.
We intentionally left this out as per the direction on the parent issue. Feedback should be "enabled for all blocks"
There was a problem hiding this comment.
We need to add setup/installation steps.
e6b5f21 to
7ff2ba0
Compare
8560050 to
1dcf4ac
Compare
asadali145
left a comment
There was a problem hiding this comment.
Looks good overall in testing, we should update the screenshot in PR. Also, an issue that we discussed over call is that staff debug button is hidden when we enable the feedback and chat aside at a time.
|
@asadali145 I've made the changes. Please take a look. |
- Style the trigger as a button (border + background) whether standalone or docked next to AskTIM, with a full-cover grey + red border on click - Mask the problem-block "saved/submit" notification line across the gap to the AskTIM button (JS flags problem blocks via payload.blockType)
Reinstate the XMLImportingModuleStoreRuntime guard so should_apply_to_block skips the waffle-flag / context_key lookup during course import (when the block has no resolvable course context) and gates on block type only. Restore the import-path test cases. Addresses the Sentry review finding.
The feedback trigger renders only in the learner (student) view, so the plugin does not need to load in Studio/CMS. Drop the cms.djangoapp entry point and the ProjectType.CMS settings registration; the aside still evaluates during course import via the xblock_asides.v1 entry point (guarded), and the excluded-block- types setting falls back to its default there.
34d95ac to
995441f
Compare
Handle the AskTIM chat container's whitesmoke box-shadow that covers the platform "Staff Debug Info" link on video blocks within feedback.css, so the fix holds independent of the installed ol_openedx_chat version. Give the staff-info row its own stacking context and matching background, scoped to video blocks only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Can you please add a step in installation guide to enable the asides from LMS admin?
The docked line-mask box-shadow projected 12px of white but the gap to the AskTIM button is only 10px, so it painted over the button's left border. Match the offset to the gap so the mask fills it without overlap. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Asides only render when XBlockAsidesConfig is enabled (off by default), so document enabling it and checking the disabled_blocks list as part of installation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
asadali145
left a comment
There was a problem hiding this comment.
LGTM!
There is a slight room for improvement for Mobile and tablet design but we can revisit it later. Also, we will create an issue to do R&D to improve/ease styling of asides.

What are the relevant tickets?
Per-block content feedback — subtask mitodl/hq#11815 (RFC mitodl/hq#11812, parent mitodl/hq#11629).
Companion PRs:
mitodl/smoot-design#241— feedback drawer UI bundlemitodl/lehrer#83— Learning MFE slot wiringmitodl/mit-learn#3593— backend endpoint + modelDescription (What does it do?)
Adds
ol_openedx_feedback, a lean trigger-onlyXBlockAsidethat renders a "Send feedback" megaphone on applicable blocks in the LMS student view. Feedback is not persisted in edx-platform — it is collected by the Learning MFE drawer and submitted to mit-learn.course/chapter/sequential/vertical); gated per course by theol_openedx_feedback.feedback_enabledCourseWaffleFlag.postMessagesol-feedback::drawer-opento the parent (Learning MFE) window with block context (courseId,blockUsageKey,blockType,blockDisplayName).apps.pyis aside-only (plugin_app = {}). The MFE owns the submit URL and enriches the payload withcourse_name/unit_title/url.Screenshot:
Audit Mode:

Staff Mode:

How can this be tested?
ol_openedx_feedback.feedback_enabledCourseWaffleFlag (globally or per course).pytest src/ol_openedx_feedback/tests— coversshould_apply_to_block(waffle-flag gate + leaf-block filter + course-import branch) and student-view rendering (authenticated vs anonymous/author).Additional Context
The drawer UI ships in smoot-design (#241), the MFE slot wiring in lehrer (#83), and submissions are stored by mit-learn (#3593). This PR is the LMS-side trigger only.