Skip to content

Commit bf1c5ba

Browse files
Merge pull request #3201 from ResearchHub/priority_proposals_earn_tab
Prioritizing Proposals for Earn Page Sorting
2 parents 9cb78b3 + 7fd4966 commit bf1c5ba

File tree

2 files changed

+207
-3
lines changed

2 files changed

+207
-3
lines changed

src/reputation/tests/test_bounties.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
from researchhub_comment.constants.rh_comment_thread_types import PEER_REVIEW
2323
from researchhub_comment.models import RhCommentModel, RhCommentThreadModel
2424
from researchhub_comment.tests.helpers import create_rh_comment
25+
from researchhub_document.related_models.constants.document_type import (
26+
PREREGISTRATION,
27+
)
28+
from researchhub_document.related_models.researchhub_post_model import ResearchhubPost
29+
from researchhub_document.related_models.researchhub_unified_document_model import (
30+
ResearchhubUnifiedDocument,
31+
)
2532
from user.models import User
2633
from user.related_models.user_model import FOUNDATION_REVENUE_EMAIL
2734
from user.tests.helpers import create_moderator, create_random_default_user, create_user
@@ -1642,6 +1649,171 @@ def test_bounty_dao_fee_goes_to_community_revenue_account(self):
16421649
).latest("created_date")
16431650
self.assertEqual(dao_fee_distribution.recipient, community_revenue_user)
16441651

1652+
def test_proposal_review_bounties_appear_first(self):
1653+
"""REVIEW bounties on PREREGISTRATION docs should sort before others."""
1654+
# Arrange
1655+
self.client.force_authenticate(self.user)
1656+
1657+
paper_bounty_res = self.client.post(
1658+
"/api/bounty/",
1659+
{
1660+
"amount": 100,
1661+
"item_content_type": self.comment._meta.model_name,
1662+
"item_object_id": self.comment.id,
1663+
"bounty_type": Bounty.Type.REVIEW,
1664+
},
1665+
)
1666+
self.assertEqual(paper_bounty_res.status_code, 201)
1667+
1668+
prereg_doc = ResearchhubUnifiedDocument.objects.create(
1669+
document_type=PREREGISTRATION,
1670+
)
1671+
prereg_post = ResearchhubPost.objects.create(
1672+
title="Proposal Post",
1673+
created_by=self.user,
1674+
document_type=PREREGISTRATION,
1675+
unified_document=prereg_doc,
1676+
)
1677+
prereg_comment = create_rh_comment(
1678+
post=prereg_post, created_by=self.recipient
1679+
)
1680+
proposal_bounty_res = self.client.post(
1681+
"/api/bounty/",
1682+
{
1683+
"amount": 100,
1684+
"item_content_type": prereg_comment._meta.model_name,
1685+
"item_object_id": prereg_comment.id,
1686+
"bounty_type": Bounty.Type.REVIEW,
1687+
},
1688+
)
1689+
self.assertEqual(proposal_bounty_res.status_code, 201)
1690+
1691+
# Act
1692+
res = self.client.get(
1693+
"/api/bounty/",
1694+
{"bounty_type": [Bounty.Type.REVIEW]},
1695+
)
1696+
1697+
# Assert
1698+
self.assertEqual(res.status_code, 200)
1699+
ids = [b["id"] for b in res.data["results"]]
1700+
proposal_idx = ids.index(proposal_bounty_res.data["id"])
1701+
paper_idx = ids.index(paper_bounty_res.data["id"])
1702+
self.assertLess(
1703+
proposal_idx,
1704+
paper_idx,
1705+
"Proposal review bounties should appear before other review bounties",
1706+
)
1707+
1708+
def test_proposal_review_bounties_appear_first_in_get_bounties(self):
1709+
"""get_bounties action should also prioritize proposal reviews."""
1710+
# Arrange
1711+
self.client.force_authenticate(self.user)
1712+
1713+
paper_bounty_res = self.client.post(
1714+
"/api/bounty/",
1715+
{
1716+
"amount": 100,
1717+
"item_content_type": self.comment._meta.model_name,
1718+
"item_object_id": self.comment.id,
1719+
"bounty_type": Bounty.Type.REVIEW,
1720+
},
1721+
)
1722+
self.assertEqual(paper_bounty_res.status_code, 201)
1723+
1724+
prereg_doc = ResearchhubUnifiedDocument.objects.create(
1725+
document_type=PREREGISTRATION,
1726+
)
1727+
prereg_post = ResearchhubPost.objects.create(
1728+
title="Proposal Post",
1729+
created_by=self.user,
1730+
document_type=PREREGISTRATION,
1731+
unified_document=prereg_doc,
1732+
)
1733+
prereg_comment = create_rh_comment(
1734+
post=prereg_post, created_by=self.recipient
1735+
)
1736+
proposal_bounty_res = self.client.post(
1737+
"/api/bounty/",
1738+
{
1739+
"amount": 100,
1740+
"item_content_type": prereg_comment._meta.model_name,
1741+
"item_object_id": prereg_comment.id,
1742+
"bounty_type": Bounty.Type.REVIEW,
1743+
},
1744+
)
1745+
self.assertEqual(proposal_bounty_res.status_code, 201)
1746+
1747+
# Act
1748+
res = self.client.get("/api/bounty/get_bounties/")
1749+
1750+
# Assert
1751+
self.assertEqual(res.status_code, 200)
1752+
ids = [b["id"] for b in res.data]
1753+
proposal_idx = ids.index(proposal_bounty_res.data["id"])
1754+
paper_idx = ids.index(paper_bounty_res.data["id"])
1755+
self.assertLess(
1756+
proposal_idx,
1757+
paper_idx,
1758+
"Proposal review bounties should appear first in get_bounties",
1759+
)
1760+
1761+
def test_preregistration_first_with_personalized_sort(self):
1762+
"""Personalized sort should still prioritize proposal reviews."""
1763+
# Arrange
1764+
self.client.force_authenticate(self.user)
1765+
1766+
paper_bounty_res = self.client.post(
1767+
"/api/bounty/",
1768+
{
1769+
"amount": 100,
1770+
"item_content_type": self.comment._meta.model_name,
1771+
"item_object_id": self.comment.id,
1772+
"bounty_type": Bounty.Type.REVIEW,
1773+
},
1774+
)
1775+
self.assertEqual(paper_bounty_res.status_code, 201)
1776+
1777+
prereg_doc = ResearchhubUnifiedDocument.objects.create(
1778+
document_type=PREREGISTRATION,
1779+
)
1780+
prereg_post = ResearchhubPost.objects.create(
1781+
title="Proposal Post",
1782+
created_by=self.user,
1783+
document_type=PREREGISTRATION,
1784+
unified_document=prereg_doc,
1785+
)
1786+
prereg_comment = create_rh_comment(
1787+
post=prereg_post, created_by=self.recipient
1788+
)
1789+
proposal_bounty_res = self.client.post(
1790+
"/api/bounty/",
1791+
{
1792+
"amount": 100,
1793+
"item_content_type": prereg_comment._meta.model_name,
1794+
"item_object_id": prereg_comment.id,
1795+
"bounty_type": Bounty.Type.REVIEW,
1796+
},
1797+
)
1798+
self.assertEqual(proposal_bounty_res.status_code, 201)
1799+
1800+
# Act
1801+
res = self.client.get(
1802+
"/api/bounty/",
1803+
{"sort": "personalized"},
1804+
)
1805+
1806+
# Assert
1807+
self.assertEqual(res.status_code, 200)
1808+
ids = [b["id"] for b in res.data["results"]]
1809+
proposal_idx = ids.index(proposal_bounty_res.data["id"])
1810+
paper_idx = ids.index(paper_bounty_res.data["id"])
1811+
self.assertLess(
1812+
proposal_idx,
1813+
paper_idx,
1814+
"Proposal review bounties should appear first with personalized sort",
1815+
)
1816+
16451817

16461818
class BountyAssessmentPhaseTests(APITestCase):
16471819
"""Tests for the bounty assessment phase functionality."""

src/reputation/views/bounty_view.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@
22

33
from django.contrib.contenttypes.models import ContentType
44
from django.db import transaction
5-
from django.db.models import DecimalField, F, OuterRef, Q, Subquery, Sum, Value
5+
from django.db.models import (
6+
Case,
7+
DecimalField,
8+
F,
9+
IntegerField,
10+
OuterRef,
11+
Q,
12+
Subquery,
13+
Sum,
14+
Value,
15+
When,
16+
)
617
from django.db.models.functions import Coalesce
718
from django.http import Http404
819
from django.shortcuts import get_object_or_404
@@ -31,6 +42,7 @@
3142
FILTER_BOUNTY_CLOSED,
3243
FILTER_BOUNTY_OPEN,
3344
FILTER_HAS_BOUNTY,
45+
PREREGISTRATION,
3446
SORT_BOUNTY_EXPIRATION_DATE,
3547
SORT_BOUNTY_TOTAL_AMOUNT,
3648
)
@@ -630,6 +642,21 @@ def filter_queryset(self, queryset):
630642
# Apply the combined filter
631643
return queryset.filter(applied_filters)
632644

645+
@staticmethod
646+
def _prioritize_preregistration_bounties(queryset):
647+
"""Annotate and sort so REVIEW bounties on proposals appear first."""
648+
return queryset.annotate(
649+
preregistration_first=Case(
650+
When(
651+
bounty_type=Bounty.Type.REVIEW,
652+
unified_document__document_type=PREREGISTRATION,
653+
then=Value(0),
654+
),
655+
default=Value(1),
656+
output_field=IntegerField(),
657+
)
658+
)
659+
633660
def list(self, request, *args, **kwargs):
634661
sort = self.request.query_params.get("sort", "-created_date")
635662

@@ -645,6 +672,8 @@ def list(self, request, *args, **kwargs):
645672

646673
queryset = Bounty.objects.filter(id__in=[b.id for b in bounties])
647674
queryset = self.filter_queryset(queryset)
675+
queryset = self._prioritize_preregistration_bounties(queryset)
676+
queryset = queryset.order_by("preregistration_first", "-created_date")
648677
else:
649678
queryset = self.filter_queryset(self.get_queryset())
650679

@@ -680,8 +709,8 @@ def list(self, request, *args, **kwargs):
680709
)
681710
)
682711

683-
# Apply sorting
684-
queryset = queryset.order_by(sort)
712+
queryset = self._prioritize_preregistration_bounties(queryset)
713+
queryset = queryset.order_by("preregistration_first", sort)
685714

686715
page = self.paginate_queryset(queryset)
687716
context = self._get_retrieve_context()
@@ -720,6 +749,9 @@ def list(self, request, *args, **kwargs):
720749
def get_bounties(self, request):
721750
qs = self.filter_queryset(self.get_queryset()).filter(
722751
parent__isnull=True, unified_document__is_removed=False
752+
)
753+
qs = self._prioritize_preregistration_bounties(qs).order_by(
754+
"preregistration_first", "-created_date"
723755
)[:10]
724756

725757
context = self._get_retrieve_context()

0 commit comments

Comments
 (0)