extensions-website/reviewers/signals.py
Oleg Komarov 13ac2436ad ApprovalQueue: use a materialized table (#240)
see #238

This change improves the listing performance: old code had to process all
ApprovalActivity to compute an extension's moderation status and position in
the queue.

Now we maintain a sortkey, a reference to the latest "meaningful" activity
object, and a total comment count. These fields are updated in a post_save
signal.

"Meaningful" activity means moderation status changes:
approved, awaiting changes, awaiting review.

"Non-meaningful" activity shouldn't affect queue position anymore and
extensions without "meaningful" activity should not appear in the queue, but
their respective detail pages should still be reachable via a direct link.
This UX may still need improvement, and #210 may be relevant here.

Reviewed-on: #240
Reviewed-by: Anna Sirota <annasirota@noreply.localhost>
2024-08-23 15:11:45 +02:00

75 lines
2.3 KiB
Python

from actstream import action
from actstream.actions import follow
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from constants.activity import Flag, Verb
from reviewers.models import ApprovalActivity, ApprovalQueue
@receiver(post_save, sender=ApprovalActivity)
def _create_action_from_review_and_follow(
sender: object,
instance: ApprovalActivity,
created: bool,
raw: bool,
**kwargs: object,
) -> None:
if raw:
return
if not created:
return
# automatically follow after an interaction
# if a user had unfollowed this extension before,
# we are making them a follower again
follow(instance.user, instance.extension, send_action=False, flag=Flag.REVIEWER)
activity_type2verb = {
ApprovalActivity.ActivityType.APPROVED: Verb.APPROVED,
ApprovalActivity.ActivityType.AWAITING_CHANGES: Verb.REQUESTED_CHANGES,
ApprovalActivity.ActivityType.AWAITING_REVIEW: Verb.REQUESTED_REVIEW,
ApprovalActivity.ActivityType.COMMENT: Verb.COMMENTED,
ApprovalActivity.ActivityType.UPLOADED_NEW_VERSION: Verb.UPLOADED_NEW_VERSION,
}
action.send(
instance.user,
verb=activity_type2verb.get(instance.type),
action_object=instance,
target=instance.extension,
)
@receiver(post_save, sender=ApprovalActivity)
def _update_approval_queue(
sender: object,
instance: ApprovalActivity,
created: bool,
raw: bool,
**kwargs: object,
) -> None:
if raw:
return
if not created:
return
activity_count = ApprovalActivity.objects.filter(extension=instance.extension).count()
if instance.type in ApprovalActivity.STATUS_CHANGE_TYPES:
ApprovalQueue.objects.update_or_create(
extension=instance.extension,
defaults={
'activity_count': activity_count,
'latest_activity': instance,
'sortkey': instance.queue_sortkey,
},
)
else:
if item := ApprovalQueue.objects.filter(extension=instance.extension).first():
item.activity_count = activity_count
item.save(update_fields={'activity_count'})
@receiver(pre_delete, sender=ApprovalActivity)
def _log_deletion(sender: object, instance: ApprovalActivity, **kwargs: object) -> None:
instance.record_deletion()