-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Edly: Integrate Forum V2 Application into edx-platform #35671
base: master
Are you sure you want to change the base?
Conversation
24a00ff
to
59a0227
Compare
59a0227
to
dba51e4
Compare
@Faraz32123 Are the unit tests complete now? Did you test the new forum v2 with the discussions MFE? |
|
@@ -82,6 +82,7 @@ def _set_mock_request_data(self, mock_request, data): | |||
|
|||
|
|||
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True) | |||
@patch('lms.djangoapps.discussion.toggles.ENABLE_FORUM_V2.is_enabled', autospec=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use @override_waffle_flag(ENABLE_FORUM_V2, True)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that can be also used but the idea was to keep the extra patching/mocking method same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With that method you wouldn't need to pass around the mock, and eventually when forums v2 is the only one you can just drop the relevant code easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have created test files(those will come in next PR) that cover forum v2. So in case when we are only left with forum v2, we'll eventually remove this file completely.
can @regisb you or someone with access run the workflows again. Thanks. |
@Faraz32123, @regisb: What do you need at this point in order to get this PR merged? |
@ormsbee All that's needed is a 👍 review. It's a fairly big PR, so if you want we can walk you through the changes in a live call. Who do you think would be most suited to review this? We can find someone internally with expertise on cs_comments_service. |
@hurtstotouchfire: Wanted to make sure you folks were aware of this PR. This introduces the switching code between old and new forums. It should not affect the default site behavior, in that it will route to the I know you've said that 2U doesn't need to be in the loop for PRs as long as the default behaviors are unchanged for non-tutor users. But I wanted to make sure you and your team had a chance to review if you wanted, and that you knew this was coming down the pipe in case you notice any regressions. I'm doing a review as well, and we'll target a merge for later this week. |
400dc29
to
4a75633
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still need to look through the test code properly, and probably take a second look at models.py
, but I wanted to put these comments down sooner rather than later. Thanks folks.
lms/djangoapps/discussion/toggles.py
Outdated
f"{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe", __name__ | ||
) | ||
|
||
FORUM_V2_WAFFLE_FLAG_NAMESPACE = "forum_v2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The waffle flag namespace is meant to represent the app (or group of apps), not the specific feature. You don't need to define a new one here–please just re-use the imported WAFFLE_FLAG_NAMESPACE.
lms/djangoapps/discussion/toggles.py
Outdated
# .. toggle_use_cases: temporary, open_edx | ||
# .. toggle_creation_date: 2024-9-26 | ||
# .. toggle_target_removal_date: 2025-12-05 | ||
ENABLE_FORUM_V2 = CourseWaffleFlag( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason that this is defined here, instead of in https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/discussions/config/waffle.py like most of the others? If there is no strong reason, please move it there so that we aren't importing stuff from the lms
package into openedx.core
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we can move it to openedx.core
. There's no reason as such.
def get_course_key(course_id): | ||
if course_id and isinstance(course_id, str): | ||
course_id = CourseKey.from_string(course_id) | ||
return course_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this function is written to be a little permissive in its inputs, e.g. you can send it a CourseKey
and get it back unmodified, None
will get returned. But a blank string would raise an InvalidKeyError
.
I think that's okay, but please add a docstring (and optionally a type annotation) explaining what this function is expecting to get and why. Without that, it's hard to understand what the edge case behavior is intended to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, we can add a docstring/type annotation to explain well.
) | ||
course_id = self.attributes.get("course_id") or kwargs.get("course_id") | ||
if not course_id: | ||
course_id = forum_api.get_course_id_by_comment(self.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[question]: What would cause us to fall through to this situation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, course_id
wasn't being used with all API calls(or i can say course_id wasn't being passed from UI and there wasn't any other way to get course_id here in the API call). But to use the CourseWaffleFlag, we needed the course_id
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was actually one of the major headaches caused by the introduction of the course waffle flags: not all API calls pass a course ID. In this particular case, a comment ID is passed, but not a course ID. So to check the course waffle flag value, we need to first find the course ID that is associated to that comment ID.
if not params.get("course_id"): | ||
params = query_params['course_id'] | ||
response = forum_api.get_user_threads(**params) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused about how this works. Is query_params['course_id']
a dict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, query_params
is a dict.
You can view it here and on line 155.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That link doesn't work for me.
I get that query_params
is a dict. But here it looks like we're saying, "If the course_id
in params
is missing/blank/null, then replace all of params
with query_params['course_id']
".
I'm not clear what lines 82 and 83 are doing here. If line 83 executes and query_params['course_id']
is a string, then the call to get_user_threads(**params)
will fail. If line 83 was meant to be params['course_id'] = query_params['course_id']
, then it seems redundant given how params
is initially defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the link: https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/discussion/views.py.
Yup, you are right, the code is redundant here as params
is already getting course_id
above in code from query_params
and params is a dict(good catch).
course_key = utils.get_course_key(course_id) | ||
if is_forum_v2_enabled(course_key): | ||
if user_id := request_params.get('user_id'): | ||
request_params['user_id'] = str(user_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[question (not a request for change)]: user_id
seems to get passed around various things as strings, while group_id
gets passed around as ints. What's the reasoning for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's right. Basically V1(cs_comment_service
) was saving user_id
as strings and group_id
as int in mongodb. So, we wanted to replicate the same behaviour as that of V1 in forum_V2 for mongo and then same in mysql.
try: | ||
response = forum_api.get_user(self.attributes["id"], retrieve_params) | ||
except ForumV2RequestError as e: | ||
self.save({"course_key": course_key}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[question] Should error logging happen here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't need an error log here I think.
The use case for this catch here is when a new user opens the discussion forum(UI) first time. It tries to get the user from mongo/mysql but as it doesn't exists, it just creates it there and then get it again in the catch(except) block to continue.
Hope it answers the question.
9337c7c
to
5e2da74
Compare
Course waffle flag name was updated in the latest edx-platform PR: openedx/edx-platform#35671
5e2da74
to
46ac5da
Compare
This commit introduces the new Forum V2 application, allowing users to choose between the legacy Forum V1 and the new Forum V2 at the course level. Key Changes: - Added waffle flag `discussions.enable_forum_v2` to enable Forum V2 for selected courses, allowing coexistence with Forum V1. - Default data storage for Forum V2 is set to MongoDB, with an option to switch to MySQL using the waffle flag `forum_v2.enable_mysql_backend`. - Introduced management command `forum_migrate_course_from_mongodb_to_mysql` for per-course data migration from MongoDB to MySQL. Note: This PR does not include all unit tests for the Forum V2 native API due to ongoing migration efforts. Further updates will follow to ensure full test coverage before final release. Co-authored-by: [Muhammad Faraz Maqsood] <[email protected]> Co-authored-by: [Ali Salman] <[email protected]>
46ac5da
to
d14b3b2
Compare
Course waffle flag name was updated in the latest edx-platform PR: openedx/edx-platform#35671
This PR introduces the integration of the new Forum V2 application into the edx-platform, allowing a course-level choice between the legacy forum (V1) and the new Forum V2.
Key Changes 🚀🚀:
Waffle Flag for Forum V2:
A new course waffle flag,
discussions.enable_forum_v2
, has been introduced. When enabled, it will activate the Forum V2 application for the selected course, allowing Forum V1 and V2 to coexist in the platform, used on a per-course basis.Data Storage Options:
By default, Forum V2 stores data in MongoDB. However, if MySQL is preferred (as is the default in Tutor), an additional course waffle flag,
forum_v2.enable_mysql_backend
, can be used to switch the storage backend to MySQL.Course-Level Data Migration:
A new management command,
forum_migrate_course_from_mongodb_to_mysql
, enables data migration on a per-course basis from MongoDB to MySQL. This provides a smooth transition for courses already using Forum V2 with MongoDB who wish to switch to MySQL.🛠 Note that this PR does not include all unit tests for the forum v2 native API. This is because migrating unit tests is taking much longer than expected. We will take out this PR from draft as soon as it is considered production-ready, despite the fact that some code in edx-platform might not be 100% covered.