Skip to content

Commit

Permalink
Merge pull request #4876 from vince0656/remarket-resurface
Browse files Browse the repository at this point in the history
#4745: Surface re-marketed issues to the top of the issue explorer
  • Loading branch information
octavioamu authored Aug 14, 2019
2 parents a0b5de8 + c9eba17 commit a619aa7
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 24 deletions.
37 changes: 20 additions & 17 deletions app/assets/v2/js/pages/bounty_details.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ const isAvailableIfReserved = function(bounty) {

const isBountyOwner = result => {
if (typeof web3 == 'undefined' || !web3.eth ||
typeof web3.eth.coinbase == 'undefined' || !web3.eth.coinbase) {
typeof web3.eth.coinbase == 'undefined' || !web3.eth.coinbase || !result) {
return false;
}
return caseInsensitiveCompare(web3.eth.coinbase, result['bounty_owner_address']);
Expand Down Expand Up @@ -1170,19 +1170,6 @@ var do_actions = function(result) {
actions.push(_entry);
}

if (show_extend_deadline) {
const enabled = true;
const _entry = {
enabled: enabled,
href: '/extend-deadlines',
text: gettext('Extend Expiration'),
parent: 'bounty_actions',
title: gettext('Extend deadline of an issue')
};

actions.push(_entry);
}

if (show_invoice) {
const _entry = {
enabled: true,
Expand All @@ -1196,14 +1183,30 @@ var do_actions = function(result) {
}

if (show_change_bounty) {
const connector_char = result['url'].indexOf('?') == -1 ? '?' : '&';

const _entry = [
{
enabled: true,
href: '/bounty/change/' + result['pk'],
text: gettext('Edit Issue Details'),
text: gettext('Edit Issue'),
parent: 'bounty_actions',
title: gettext('Update your Bounty Settings to get the right Crowd')
}
title: gettext('Update your Bounty Settings to get the right Crowd'),
edit_dropdown: true,
edit_issue_href: '/bounty/change/' + result['pk'],
show_extend_expiration: show_extend_deadline,
extend_expiration_href: '/extend-deadlines',
show_remarket: true,
remarket_enabled: result['can_remarket'],
remarket_url: result['url'] + connector_char + 'trigger_remarket=1'
}// ,
// {
// enabled: true,
// href: '/issue/refund_request?pk=' + result['pk'],
// text: gettext('Request Fee Refund'),
// parent: 'right_actions',
// title: gettext('Raise a request if you believe you need your fee refunded')
// }
];

actions.push(..._entry);
Expand Down
4 changes: 4 additions & 0 deletions app/dashboard/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ def create_new_bounty(old_bounties, bounty_payload, bounty_details, bounty_id):
'web3_created': timezone.make_aware(
timezone.datetime.fromtimestamp(bounty_payload.get('created')),
timezone=UTC),
'last_remarketed': timezone.make_aware(
timezone.datetime.fromtimestamp(bounty_payload.get('created')),
timezone=UTC),
'remarketed_count': 0,
'github_url': url,
'token_name': token_name,
'token_address': token_address,
Expand Down
23 changes: 23 additions & 0 deletions app/dashboard/migrations/0042_auto_20190718_1749.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 2.1.7 on 2019-07-18 17:49

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0041_auto_20190718_1222'),
]

operations = [
migrations.AddField(
model_name='bounty',
name='last_remarketed',
field=models.DateTimeField(blank=True, db_index=True, null=True),
),
migrations.AddField(
model_name='bounty',
name='remarketed_count',
field=models.PositiveSmallIntegerField(blank=True, default=0, null=True),
),
]
14 changes: 14 additions & 0 deletions app/dashboard/migrations/0048_merge_20190808_1934.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 2.1.7 on 2019-08-08 19:34

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0047_auto_20190808_1206'),
('dashboard', '0042_auto_20190718_1749'),
]

operations = [
]
19 changes: 19 additions & 0 deletions app/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ class Bounty(SuperModel):
is_featured = models.BooleanField(
default=False, help_text=_('Whether this bounty is featured'))
featuring_date = models.DateTimeField(blank=True, null=True, db_index=True)
last_remarketed = models.DateTimeField(blank=True, null=True, db_index=True)
remarketed_count = models.PositiveSmallIntegerField(default=0, blank=True, null=True)
fee_amount = models.DecimalField(default=0, decimal_places=18, max_digits=50)
fee_tx_id = models.CharField(default="0x0", max_length=255, blank=True)
coupon_code = models.ForeignKey('dashboard.Coupon', blank=True, null=True, related_name='coupon', on_delete=models.SET_NULL)
Expand Down Expand Up @@ -1141,6 +1143,23 @@ def reserved_for_user_handle(self, handle):

self.bounty_reserved_for_user = profile

@property
def can_remarket(self):
result = True

if self.remarketed_count >= 2:
result = False

if self.last_remarketed:
one_hour_after_remarketing = self.last_remarketed + timezone.timedelta(hours=1)
if timezone.now() < one_hour_after_remarketing:
result = False

if self.interested.count() > 0:
result = False

return result


class BountyFulfillmentQuerySet(models.QuerySet):
"""Handle the manager queryset for BountyFulfillments."""
Expand Down
9 changes: 6 additions & 3 deletions app/dashboard/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import time
from datetime import datetime

from django.db.models import Count
from django.db.models import Count, F

import django_filters.rest_framework
from kudos.models import KudosTransfer
Expand Down Expand Up @@ -146,7 +146,7 @@ class Meta:
'attached_job_description', 'needs_review', 'github_issue_state', 'is_issue_closed',
'additional_funding_summary', 'funding_organisation', 'paid',
'admin_override_suspend_auto_approval', 'reserved_for_user_handle', 'is_featured',
'featuring_date', 'repo_type', 'unsigned_nda', 'funder_last_messaged_on',
'featuring_date', 'repo_type', 'unsigned_nda', 'funder_last_messaged_on', 'can_remarket'
)

def create(self, validated_data):
Expand Down Expand Up @@ -380,7 +380,10 @@ def get_queryset(self):
# order
order_by = self.request.query_params.get('order_by')
if order_by and order_by != 'null':
queryset = queryset.order_by(order_by)
if order_by == 'recently_marketed':
queryset = queryset.order_by(F('last_remarketed').desc(nulls_last = True), '-web3_created')
else:
queryset = queryset.order_by(order_by)

queryset = queryset.distinct()

Expand Down
20 changes: 17 additions & 3 deletions app/dashboard/templates/bounty/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,23 @@ <h5 class="bounty-heading">{% trans "Funder" %}</h5>
[[/if]]
</a>
[[else]]
<a class="button button--primary [[if !enabled]]disabled[[/if]] [[:buttonclass]]" role="button" href="[[:href]]" target="[[:target]]" [[if modal]] data-toggle="modal" data-target=".share-modal" [[/if]]>
<span class="font-caption">[[:text]]</span>
</a>
[[if edit_dropdown]]
<div class="dropdown show" style="display: inline;">
<a class="button button--primary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% trans "Update Issue" %}
</a>

<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="[[:edit_issue_href]]">{% trans "Edit Issue Details" %}</a>
[[if show_extend_expiration]] <a class="dropdown-item" href="[[:extend_expiration_href]]">{% trans "Extend Expiration" %}</a> [[/if]]
[[if show_remarket]] <a class="dropdown-item [[if !remarket_enabled]]disabled[[/if]]" href="[[:remarket_url]]">{% trans "Re-market Issue" %}</a> [[/if]]
</div>
</div>
[[else]]
<a class="button button--primary [[if !enabled]]disabled[[/if]] [[:buttonclass]]" role="button" href="[[:href]]" target="[[:target]]" [[if modal]] data-toggle="modal" data-target=".share-modal" [[/if]]>
<span class="font-caption">[[:text]]</span>
</a>
[[/if]]
[[/if]]
</span>
</script>
Expand Down
1 change: 1 addition & 0 deletions app/dashboard/templates/shared/search_bar.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<div class="col-8 font-caption order_by">
<div class="heading">{% trans "Sort by" %} </div>
<select name="sort_option" id="sort_option">
<option value="recently_marketed">{% trans "Recently Marketed" %}</option>
<option value="-web3_created">{% trans "Created: Recent" %}</option>
<option value="web3_created">{% trans "Created: Oldest" %}</option>
<option value="-_val_usd_db" {% if hackathon %}selected{% endif %}>{% trans "Value: Highest" %}</option>
Expand Down
75 changes: 75 additions & 0 deletions app/dashboard/tests/test_dashboard_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def test_bounty():
assert bounty.estimated_hours == 7
assert bounty_fulfillment.profile.handle == 'fred'
assert bounty_fulfillment.bounty.title == 'foo'
assert bounty.remarketed_count == 0
assert bounty.last_remarketed is None

@staticmethod
def test_exclude_bounty_by_status():
Expand Down Expand Up @@ -343,6 +345,79 @@ def test_bounty_expired():
)
assert bounty.status == 'expired'

@staticmethod
def test_can_remarket_is_true_under_valid_conditions():
bounty = Bounty.objects.create(
title='CanRemarketTrueTest',
idx_status=0,
is_open=True,
web3_created=datetime(2008, 10, 31, tzinfo=pytz.UTC),
expires_date=datetime(2008, 11, 30, tzinfo=pytz.UTC),
last_remarketed=datetime(2008, 10, 31, tzinfo=pytz.UTC),
github_url='https://github.com/gitcoinco/web/issues/12345678',
raw_data={}
)

assert bounty.last_remarketed == datetime(2008, 10, 31, tzinfo=pytz.UTC)
assert bounty.remarketed_count == 0
assert bounty.can_remarket is True

@staticmethod
def test_can_remarket_is_false_if_remarket_count_2():
bounty = Bounty.objects.create(
title='CanRemarketFalseTest',
idx_status=0,
is_open=True,
web3_created=datetime(2008, 10, 31, tzinfo=pytz.UTC),
expires_date=datetime(2008, 11, 30, tzinfo=pytz.UTC),
last_remarketed=datetime(2008, 10, 31, tzinfo=pytz.UTC),
remarketed_count=2,
github_url='https://github.com/gitcoinco/web/issues/12345678',
raw_data={}
)

assert bounty.can_remarket is False

@staticmethod
def test_can_remarket_is_false_if_remarketed_within_last_hour():
now = datetime.now(pytz.UTC)
bounty = Bounty.objects.create(
title='CanRemarketFalseTest',
idx_status=0,
is_open=True,
web3_created=now,
expires_date=now + timedelta(hours=1),
last_remarketed=now,
github_url='https://github.com/gitcoinco/web/issues/12345678',
raw_data={}
)

assert bounty.can_remarket is False

@staticmethod
def test_can_remarket_is_false_if_workers_have_applied():
bounty = Bounty.objects.create(
title='CanRemarketFalseTest',
idx_status=0,
is_open=True,
web3_created=datetime(2008, 10, 31, tzinfo=pytz.UTC),
expires_date=datetime(2008, 11, 30, tzinfo=pytz.UTC),
last_remarketed=datetime(2008, 10, 31, tzinfo=pytz.UTC),
github_url='https://github.com/gitcoinco/web/issues/12345678',
raw_data={}
)

dummy_profile = Profile.objects.create(
handle='foo',
data={}
)

bounty.interested.create(
profile=dummy_profile
)

assert bounty.can_remarket is False

@staticmethod
def test_tip():
"""Test the dashboard Tip model."""
Expand Down
1 change: 1 addition & 0 deletions app/dashboard/tests/test_dashboard_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def test_humanize_event_name():
"""Test the humanized representation of an event name."""
assert humanize_event_name('start_work') == 'WORK STARTED'
assert humanize_event_name('remarket_funded_issue') == 'REMARKET_FUNDED_ISSUE'
assert humanize_event_name('issue_remarketed') == 'ISSUE RE-MARKETED'

@staticmethod
@patch('dashboard.utils.UserAction.objects')
Expand Down
3 changes: 2 additions & 1 deletion app/dashboard/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ def humanize_event_name(name):
'killed_bounty': 'Cancelled funded issue',
'worker_approved': 'Worker approved',
'worker_rejected': 'Worker rejected',
'work_done': 'Work done'
'work_done': 'Work done',
'issue_remarketed': 'Issue re-marketed'
}

return humanized_event_names.get(name, name).upper()
Expand Down
39 changes: 39 additions & 0 deletions app/dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,44 @@ def helper_handle_approvals(request, bounty):
messages.warning(request, _('Only the funder of this bounty may perform this action.'))


def helper_handle_remarket_trigger(request, bounty):
trigger_remarket = request.GET.get('trigger_remarket', False)
if trigger_remarket:
is_staff = request.user.is_staff
is_funder = bounty.is_funder(request.user.username.lower())
if is_staff or is_funder:
remarketed_count = bounty.remarketed_count
if remarketed_count < 2:
one_hour_after_remarketing = bounty.last_remarketed + timezone.timedelta(hours=1)
now = timezone.now()
if now > one_hour_after_remarketing:
bounty.remarketed_count = remarketed_count + 1
bounty.last_remarketed = now
bounty.save()

maybe_market_to_slack(bounty, 'issue_remarketed')

further_permitted_remarket_count = 2 - bounty.remarketed_count
success_msg = "This issue has been remarketed. The issue will appear at the top of the issue explorer. "
if further_permitted_remarket_count == 1:
success_msg = success_msg + "You will be able to remarket this bounty one more time if a contributor does not pick this up."
elif further_permitted_remarket_count == 0:
success_msg = success_msg + "Please note this is the last time the issue is able to be remarketed."

messages.success(request, _(success_msg))

else:
time_delta_wait_time = one_hour_after_remarketing - now
minutes_to_wait = round(time_delta_wait_time.total_seconds() / 60)
messages.warning(request,
_(f'You need to wait {minutes_to_wait} minutes before remarketing this bounty again'))
else:
messages.warning(request,
_('You cannot remarket this bounty again due to reaching the remarket limit (2)'))
else:
messages.warning(request, _('Only staff or the funder of this bounty may do this.'))


@login_required
def bounty_invite_url(request, invitecode):
"""Decode the bounty details and redirect to correct bounty
Expand Down Expand Up @@ -1675,6 +1713,7 @@ def bounty_details(request, ghuser='', ghrepo='', ghissue=0, stdbounties_id=None
helper_handle_admin_override_and_hide(request, bounty)
helper_handle_suspend_auto_approval(request, bounty)
helper_handle_mark_as_remarket_ready(request, bounty)
helper_handle_remarket_trigger(request, bounty)
helper_handle_admin_contact_funder(request, bounty)
helper_handle_override_status(request, bounty)
except Bounty.DoesNotExist:
Expand Down

0 comments on commit a619aa7

Please sign in to comment.