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

Add a BFFE endpoint for Learning Assistant (LA), including information about LA is enabled, LA message history, and LA audit trial. #140

Merged
merged 1 commit into from
Dec 4, 2024

Conversation

MichaelRoytman
Copy link
Member

@MichaelRoytman MichaelRoytman commented Dec 2, 2024

This pull request adds a back-end-for-frontend (BFFE) endpoint for the Learning Assistant to get all the necessary data it needs to function.

The response from this endpoint includes the following information.

  • whether the Learning Assistant is enabled
  • message history information, if the learner is eligible to use the Learning Assistant
  • audit trial information

@MichaelRoytman MichaelRoytman force-pushed the michaelroytman/COSMO-567-GET-endpoint-BFFE branch 2 times, most recently from 06a3033 to 1420f66 Compare December 3, 2024 20:18
Copy link

github-actions bot commented Dec 3, 2024

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  learning_assistant
  __init__.py
  api.py
  data.py
  urls.py
  views.py 21-23
Project Total  

This report was generated by python-coverage-comment-action

@MichaelRoytman MichaelRoytman marked this pull request as ready for review December 3, 2024 20:20
@MichaelRoytman MichaelRoytman changed the title [WIP] Add a BFFE endpoint for Learning Assistant (LA), including information about LA is enabled, LA message history, and LA audit trial. Add a BFFE endpoint for Learning Assistant (LA), including information about LA is enabled, LA message history, and LA audit trial. Dec 3, 2024
@MichaelRoytman MichaelRoytman force-pushed the michaelroytman/COSMO-567-GET-endpoint-BFFE branch from 1420f66 to e9652ac Compare December 3, 2024 20:26
trial_data['start_date'] = trial.start_date
trial_data['expiration_date'] = trial.expiration_date

data['trial'] = trial_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently have the frontend extracting 'audit_trial' and not 'trial' from the GET request data. Wondering if we should name this attribute 'audit_trial', or extract the 'trial' attribute in the frontend. Let me know your thoughts!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on the question. My vote is on audit_trial for consistency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem. I chose trial because the trial is available to more than just learners in the audit track, but it's not an issue to change.

Comment on lines 32 to 36
re_path(
fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/summary',
LearningAssistantSummaryView.as_view(),
name='summary',
),
Copy link
Member

@rijuma rijuma Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have plans to enable (and integrate) the Unit Summary to Xpert and this /summary reference could be confusing. Maybe we could use something like /chat instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, are you suggesting that I change the URL/name to chat? I think that would conflict with the chat endpoint, so I would prefer not to do that. I also don't think chat represents the purpose of this endpoint.

Personally, I think summary and unit_summary would be reasonable URLs to coexist, and I wouldn't find them confusing. I don't think we should name the URL for unit summaries summary.

What about one of these? overview, details, or info?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally forgot about the existing /chat one. I think info fits better, but it's your call.

Copy link
Member

@varshamenon4 varshamenon4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! A few comments and some nits (feel free to disregard those though!).

DAYS_SINCE_UPGRADE_DEADLINE = datetime.now() - upgrade_deadline
if DAYS_SINCE_UPGRADE_DEADLINE >= timedelta(days=0):
return True
default_trial_length_days = 14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea to have a default!


# If the upgrade deadline has passed, return True for expired. Upgrade deadline is an optional attribute of a
# CourseMode, so if it's None, then do not return True.
days_since_upgrade_deadline = datetime.now() - upgrade_deadline if upgrade_deadline else None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
days_since_upgrade_deadline = datetime.now() - upgrade_deadline if upgrade_deadline else None
days_until_upgrade_deadline = datetime.now() - upgrade_deadline if upgrade_deadline else None

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I'll make that change.

@@ -13,3 +15,14 @@ class LearningAssistantCourseEnabledData:

course_key: CourseKey = field(validator=validators.instance_of(CourseKey))
enabled: bool = field(validator=validators.instance_of(bool))


@frozen
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: does this just mean that this cannot be changed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The frozen decorator makes LearningAssistantAuditTrialData class immutable, so once an instance of this class is created, its attributes cannot be changed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... just let it go.

* 200: OK
* 400: Malformed Request - Course ID is not a valid course ID.
* 403: Forbidden - Learning assistant not enabled for course or learner does not have a valid enrollment or is
not staff.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm not sure if it's just how it's rendered here, but is this spacing intended?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if you're seeing something different than what I'm seeing, but I tend to align subsequent lines of paragraphs to the beginning character of the paragraph, because I find it easier to read. Would you prefer a different indentation?

@@ -271,3 +288,125 @@ def get(self, request, course_run_id):
message_history = get_message_history(courserun_key, user, message_count)
data = MessageSerializer(message_history, many=True).data
return Response(status=http_status.HTTP_200_OK, data=data)


class LearningAssistantSummaryView(APIView):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

"""
Given a course run ID, return all the data necessary for the Learning Assistant to fuction.

The response will be in the following format.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like this format!

"""
Test suite for get_audit_trial_expiration_date.
"""
@ddt.data(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These cases look pretty complete to me!

trial_length_days = default_trial_length_days

# If LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS is set to a negative number, assume it should be 0.
# pylint: disable=consider-using-max-builtin
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pylint told me to "consider" doing trial_length_days = max(trial_length_days, 0) instead, which I thought was less readable, so I didn't. Interesting that it forces me to address something it tells me to consider 😄.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"I'm asking telling you to consider, and I won't take a NO for an answer.." 🤌

@@ -95,13 +95,6 @@ def setUp(self):
)
self.patcher.start()

@patch('learning_assistant.views.learning_assistant_enabled')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this test is here. It tests the enabled endpoint. Copypasta? I removed it.

@@ -264,13 +261,6 @@ def setUp(self):
super().setUp()
self.course_id = 'course-v1:edx+test+23'

@patch('learning_assistant.views.learning_assistant_enabled')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this test is here. It tests the enabled endpoint. Copypasta? I removed it.

return expiration_datetime


def get_audit_trial(user):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to create a get and a get_or_create method because when we make a POST call to the /chat endpoint, we want to create a trial if one doesn't exist, but when we make a GET call to the /summary endpoint, we do not.


# If the upgrade deadline has passed, return True for expired. Upgrade deadline is an optional attribute of a
# CourseMode, so if it's None, then do not return True.
days_since_upgrade_deadline = datetime.now() - upgrade_deadline if upgrade_deadline else None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to rewrite Isaac's original function a bit, so I don't want this change to get lost in the diff. The expiration_datetime field of the CourseMode model may be None, so I've added handling for that here.

@@ -38,51 +43,28 @@
class CourseChatView(APIView):
"""
View to retrieve chat response.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some missing documentation.

Comment on lines 32 to 36
re_path(
fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/summary',
LearningAssistantSummaryView.as_view(),
name='summary',
),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, are you suggesting that I change the URL/name to chat? I think that would conflict with the chat endpoint, so I would prefer not to do that. I also don't think chat represents the purpose of this endpoint.

Personally, I think summary and unit_summary would be reasonable URLs to coexist, and I wouldn't find them confusing. I don't think we should name the URL for unit summaries summary.

What about one of these? overview, details, or info?

trial_data['start_date'] = trial.start_date
trial_data['expiration_date'] = trial.expiration_date

data['trial'] = trial_data
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem. I chose trial because the trial is available to more than just learners in the audit track, but it's not an issue to change.


Accepts: [GET]

Path: /learning_assistant/v1/course_id/{course_run_id}/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Shouldn't this path include the /summary part (or whatever variant you choose for it)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Thank you for noticing that - very observant 😀.

@MichaelRoytman MichaelRoytman force-pushed the michaelroytman/COSMO-567-GET-endpoint-BFFE branch from efc1002 to 45e0393 Compare December 4, 2024 18:44
…ata to function

This commit adds a back-end-for-frontend (BFFE) endpoint for the Learning Assistant to get all the necessary data it needs to function.

The response from this endpoint includes the following information.

* whether the Learning Assistant is enabled
* message history information, if the learner is eligible to use the Learning Assistant
* audit trial information
@MichaelRoytman MichaelRoytman force-pushed the michaelroytman/COSMO-567-GET-endpoint-BFFE branch from 45e0393 to 1fc3972 Compare December 4, 2024 19:21
@MichaelRoytman MichaelRoytman merged commit ed0061e into main Dec 4, 2024
4 checks passed
@MichaelRoytman MichaelRoytman deleted the michaelroytman/COSMO-567-GET-endpoint-BFFE branch December 4, 2024 19:25
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.

4 participants