-
Notifications
You must be signed in to change notification settings - Fork 10
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
feat: [assignments] backend support for "State" and "Recent action" columns #292
Conversation
c09d31b
to
d46dee8
Compare
532df68
to
7e9cb7e
Compare
@@ -3,18 +3,23 @@ | |||
""" | |||
import logging | |||
|
|||
from django.db.models import Case, Exists, F, Max, OuterRef, Prefetch, Q, Value, When |
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 think this one line exemplifies my concerns over the last week about frontend-backend design compatibility and tech debt. There's a time and place for ORM magic, this should not have been one of them.
7e9cb7e
to
acede51
Compare
Right now this doesn't give the frontend an direct field to distinguish between "Notifying" and "Waiting" assignments, but it is sorted correctly. I'd like to address that in a separate PR tomorrow. |
acede51
to
d19497e
Compare
…olumns Assignment serializer contains three additional output keys: * `actions` (list of dict): A list of all serialized actions for the assignment. * `recent_action` (dict): intended to power the "Recent action" column in the frontend display. * `learner_state` (str): intended to power the "Status" column in the frontend display. Assignment list views are now sortable on two keys: * `learner_state_sort_order`: This sorts based on the internal sort order of dynamic field `learner_state`. * `recent_action_time`: This is a dynamic field defined as `coalesce(most recent reminder, assignment creation)` Sample URLs ----------- Listing assignments sorted by learner_state: ``` /api/v1/assignment-configurations/{assignment_configuration_uuid}/admin/assignments/?ordering=learner_state_sort_order /api/v1/assignment-configurations/{assignment_configuration_uuid}/admin/assignments/?ordering=-learner_state_sort_order ``` Listing assignments sorted by recent_action_time: ``` /api/v1/assignment-configurations/{assignment_configuration_uuid}/admin/assignments/?ordering=recent_action_time /api/v1/assignment-configurations/{assignment_configuration_uuid}/admin/assignments/?ordering=-recent_action_time ``` Sample Response Snippets ------------------------ Serialized assignment: ``` { [...] 'actions': [ { 'uuid': <action uuid>, 'action_type': <one of AssignmentActions: ["learner_linked", "notified", "reminded"]>, 'completed_at': <action completed_at timestamp>, 'error_reason': <action error message>, }, [...] ], 'recent_action': { 'action_type': <one of AssignmentRecentActionTypes: ["assigned", "reminded"]>, 'timestamp': <timestamp of recent action>, }, 'learner_state': <one of AssignmentLearnerStates: ["notifying", "waiting", "failed"]>, } ``` ENT-7780
d19497e
to
e5dda66
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.
(Experimenting with big red button)
This looks great! Two questions to maybe help with DB query count.
) | ||
recent_action = LearnerContentAssignmentRecentActionSerializer( | ||
help_text='Structured data about the most recent action. Meant to power a frontend table column.', | ||
source='get_recent_action_data', |
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.
Couldn't these use the annotated fields you added to the queryset? That would save a couple of queries per row of response data.
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.
...or it's possible that an upstream select_related()
would avoid the additional queries, and that using these functions as the source is fine.
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 think adding the select_related is a good idea. It's not perfect, but it is potentially an easier path forward than fixing (2):
- Something I maybe didn't fully communicate/document is the reason for duplicating the logic on both the python-layer and ORM-layer. I found that we use the
LearnerContentAssignmentResponseSerializer
on more than just objects that were fetched viaLearnerContentAssignmentAdminViewSet.get_queryset()
. It turns out we also use it indirectly on serializing policies from the policy viewset, as well as the django admin. Instead of spending more time figuring out how to annotate the assignment querysets in all of those cases (the policy viewset case baffles me the most), I decided to make the serializer less sensitive to whether or not the annotations got added.
@@ -62,14 +70,25 @@ def get_queryset(self): | |||
""" | |||
A base queryset to list or retrieve ``LearnerContentAssignment`` records. | |||
""" | |||
queryset = LearnerContentAssignment.objects.all() |
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.
Should we throw a select_related('actions')
on 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.
I'd love to, but it's throwing this error:
E django.core.exceptions.FieldError: Invalid field name(s) given in select_related: 'actions'. Choices are: assignment_configuration
According to the docs, select_related only works in the reverse direction with OneToOneField: https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-related
Returns: | ||
str: One of AssignmentLearnerStates, or None if the assignment doesn't map to one. | ||
""" | ||
has_notification = bool(LearnerContentAssignmentAction.objects.filter( |
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.
nit: switch to self.actions.filter(action_type=AssignmentActions.NOTIFIED)
to take advantage of any upstream select_relateds()
d91ff33
to
2c2251a
Compare
This gives the benefit of allowing us to remove duplicate code (in-memory calculations of recent_action + learner_state). ENT-7780
2c2251a
to
8fcf408
Compare
Assignment serializer contains three additional output keys:
actions
(list of dict): A list of all serialized actions for the assignment.recent_action
(dict): intended to power the "Recent action" column in the frontend display.learner_state
(str): intended to power the "Status" column in the frontend display.Assignment list views are now sortable on two keys:
learner_state_sort_order
: This sorts based on the internal sort order of dynamic fieldlearner_state
.recent_action_time
: This is a dynamic field defined ascoalesce(most recent reminder, assignment creation)
Sample URLs
Listing assignments sorted by learner_state:
Listing assignments sorted by recent_action_time:
Sample Response Snippets
Serialized assignment:
ENT-7780
Redoc preview: