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

Edly: Integrate Forum V2 Application into edx-platform #35671

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Faraz32123
Copy link
Contributor

@Faraz32123 Faraz32123 commented Oct 18, 2024

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.

@asadazam93
Copy link
Contributor

@Faraz32123 Are the unit tests complete now? Did you test the new forum v2 with the discussions MFE?

@Faraz32123
Copy link
Contributor Author

@Faraz32123 Are the unit tests complete now? Did you test the new forum v2 with the discussions MFE?

  • As for unit tests, current edx-platform tests that uses v1 are modified accordingly. Tests that uses v2 are under work and will come in a separate PR that will cover v2 native API calls in edx-platform.
  • Yup, we have tested the forum v2 with the discussion 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)
Copy link
Contributor

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)

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@Faraz32123
Copy link
Contributor Author

Faraz32123 commented Oct 25, 2024

can @regisb you or someone with access run the workflows again. Thanks.
Dependency conflicts with forum should be resolved now after we run CI again.

@ormsbee
Copy link
Contributor

ormsbee commented Nov 1, 2024

@Faraz32123, @regisb: What do you need at this point in order to get this PR merged?

@regisb
Copy link
Contributor

regisb commented Nov 1, 2024

@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.

@ormsbee
Copy link
Contributor

ormsbee commented Nov 11, 2024

@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 cs_comments_service by default. But it will allow you folks to start experimenting with the new backend.

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.

@regisb regisb force-pushed the edly/forumv2 branch 5 times, most recently from 400dc29 to 4a75633 Compare November 12, 2024 20:22
Copy link
Contributor

@ormsbee ormsbee left a 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.

f"{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe", __name__
)

FORUM_V2_WAFFLE_FLAG_NAMESPACE = "forum_v2"
Copy link
Contributor

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.

# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2024-9-26
# .. toggle_target_removal_date: 2025-12-05
ENABLE_FORUM_V2 = CourseWaffleFlag(
Copy link
Contributor

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

Copy link
Contributor Author

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.

Comment on lines 173 to 186
def get_course_key(course_id):
if course_id and isinstance(course_id, str):
course_id = CourseKey.from_string(course_id)
return course_id
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

@Faraz32123 Faraz32123 Nov 13, 2024

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.

Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@ormsbee ormsbee Nov 13, 2024

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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})
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@Faraz32123 Faraz32123 force-pushed the edly/forumv2 branch 4 times, most recently from 9337c7c to 5e2da74 Compare November 13, 2024 12:19
regisb added a commit to openedx/forum that referenced this pull request Nov 13, 2024
Course waffle flag name was updated in the latest edx-platform PR:
openedx/edx-platform#35671
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]>
regisb added a commit to openedx/forum that referenced this pull request Nov 14, 2024
Course waffle flag name was updated in the latest edx-platform PR:
openedx/edx-platform#35671
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants