Skip to content

Commit

Permalink
Merge pull request #626 from msuess/stdbounties-increasepayout
Browse files Browse the repository at this point in the history
Integrate StandardBounties increasePayout()
  • Loading branch information
owocki committed Mar 26, 2018
2 parents da308ad + 546cb85 commit 90f0883
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 7 deletions.
1 change: 1 addition & 0 deletions app/app/urls.py
Expand Up @@ -75,6 +75,7 @@
url(r'^bounty/details/?', dashboard.views.bounty_details, name='bounty_details'),
url(r'^funding/details/?', dashboard.views.bounty_details, name='funding_details'),
url(r'^legacy/funding/details/?', dashboard.views.bounty_details, name='legacy_funding_details'),
url(r'^funding/increase/?', dashboard.views.increase_bounty, name='increase_bounty'),
url(r'^funding/kill/?', dashboard.views.kill_bounty, name='kill_bounty'),
url(r'^tip/receive/?', dashboard.views.receive_tip, name='receive_tip'),
url(r'^tip/send/2/?', dashboard.views.send_tip_2, name='send_tip_2'),
Expand Down
15 changes: 15 additions & 0 deletions app/assets/v2/js/pages/bounty_details.js
Expand Up @@ -342,9 +342,11 @@ var do_actions = function(result) {
var show_github_link = result['github_url'].substring(0, 4) == 'http';
var show_submit_work = true;
var show_kill_bounty = !is_status_done && !is_status_expired && !is_status_cancelled;
var show_increase_bounty = !is_status_done && !is_status_expired && !is_status_cancelled;
var kill_bounty_enabled = isBountyOwner(result);
var submit_work_enabled = !isBountyOwner(result);
var start_stop_work_enabled = !isBountyOwner(result);
var increase_bounty_enabled = isBountyOwner(result);

if (is_legacy) {
show_start_stop_work = false;
Expand Down Expand Up @@ -420,6 +422,19 @@ var do_actions = function(result) {
actions.push(_entry);
}

if (show_increase_bounty) {
var _entry = {
href: '/funding/increase?source=' + result['github_url'],
text: 'Add Contribution',
parent: 'right_actions',
color: increase_bounty_enabled ? 'darkBlue' : 'darkGrey',
extraClass: increase_bounty_enabled ? '' : 'disabled',
title: increase_bounty_enabled ? 'Increase the funding of this bounty' : 'Can only be performed if you are the funder.'
};

actions.push(_entry);
}

if (show_kill_bounty) {
var enabled = kill_bounty_enabled;
var _entry = {
Expand Down
157 changes: 157 additions & 0 deletions app/assets/v2/js/pages/increase_bounty.js
@@ -0,0 +1,157 @@
load_tokens();

// Wait until page is loaded, then run the function
$(document).ready(function() {
$('input[name=amount]').keyup(setUsdAmount);
$('input[name=amount]').blur(setUsdAmount);
$('select[name=deonomination]').change(setUsdAmount);

$('input[name=amount]').focus();

var denomSelect = $('select[name=deonomination]');

localStorage['tokenAddress'] = denomSelect.data('tokenAddress');
localStorage['amount'] = 0;
denomSelect.select2();
$('.js-select2').each(function() {
$(this).select2();
});

// submit bounty button click
$('#submitBounty').click(function(e) {
mixpanel.track('Increase Bounty Clicked (funder)', {});

// setup
e.preventDefault();
loading_button($(this));

var issueURL = $('input[name=issueURL]').val();
var amount = $('input[name=amount]').val();
var tokenAddress = $('select[name=deonomination]').val();
var token = tokenAddressToDetails(tokenAddress);
var decimals = token['decimals'];
var tokenName = token['name'];
var decimalDivisor = Math.pow(10, decimals);

// validation
var isError = false;

if ($('#terms:checked').length == 0) {
_alert({ message: 'Please accept the terms of service.' });
isError = true;
} else {
localStorage['acceptTOS'] = true;
}
var is_issueURL_invalid = issueURL == '' ||
issueURL.indexOf('http') != 0 ||
issueURL.indexOf('github') == -1 ||
issueURL.indexOf('javascript:') != -1

;
if (is_issueURL_invalid) {
_alert({ message: 'Please enter a valid github issue URL.' });
isError = true;
}
if (amount == '') {
_alert({ message: 'Please enter an amount.' });
isError = true;
}
if (isError) {
unloading_button($(this));
return;
}
$(this).attr('disabled', 'disabled');

// setup web3
// TODO: web3 is using the web3.js file. In the future we will move
// to the node.js package. github.com/ethereum/web3.js
var isETH = tokenAddress == '0x0000000000000000000000000000000000000000';
var token_contract = web3.eth.contract(token_abi).at(tokenAddress);
var account = web3.eth.coinbase;

amount = amount * decimalDivisor;
// Create the bounty object.
// This function instantiates a contract from the existing deployed Standard Bounties Contract.
// bounty_abi is a giant object containing the different network options
// bounty_address() is a function that looks up the name of the network and returns the hash code
var bounty = web3.eth.contract(bounty_abi).at(bounty_address());

// setup inter page state
localStorage[issueURL] = JSON.stringify({
'timestamp': null,
'txid': null
});

function web3Callback(error, result) {
if (error) {
mixpanel.track('Increase Bounty Error (funder)', {step: 'post_bounty', error: error});
_alert({ message: 'There was an error. Please try again or contact support.' });
unloading_button($('#submitBounty'));
return;
}

// update localStorage issuePackage
var issuePackage = JSON.parse(localStorage[issueURL]);

issuePackage['txid'] = result;
issuePackage['timestamp'] = timestamp();
localStorage[issueURL] = JSON.stringify(issuePackage);

_alert({ message: 'Submission sent to web3.' }, 'info');
setTimeout(function() {
mixpanel.track('Submit New Bounty Success', {});
document.location.href = '/funding/details/?url=' + issueURL;
}, 1000);
}

var bountyAmount = parseInt($('input[name=valueInToken]').val(), 10);
var ethAmount = isETH ? amount : 0;
var bountyId = $('input[name=standardBountiesId]').val();
var fromAddress = $('input[name=bountyOwnerAddress]').val();

var errormsg = undefined;

if (bountyAmount == 0 || open == false) {
errormsg = 'No active funded issue found at this address. Are you sure this is an active funded issue?';
}
if (fromAddress != web3.eth.coinbase) {
errormsg = 'Only the address that submitted this funded issue may increase the payout.';
}

if (errormsg) {
_alert({ message: errormsg });
unloading_button($('#submitBounty'));
return;
}

function approveSuccessCallback() {
bounty.increasePayout(
bountyId,
bountyAmount + amount,
amount,
{
from: account,
value: ethAmount,
gasPrice: web3.toHex($('#gasPrice').val()) * Math.pow(10, 9)
},
web3Callback
);
}

if (isETH) {
// no approvals needed for ETH
approveSuccessCallback();
} else {
token_contract.approve(
bounty_address(),
amount,
{
from: account,
value: 0,
gasPrice: web3.toHex($('#gasPrice').val()) * Math.pow(10, 9)
},
approveSuccessCallback
);
}
});
});
7 changes: 0 additions & 7 deletions app/assets/v2/js/pages/new_bounty.js
@@ -1,13 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable nonblock-statement-body-position */
load_tokens();
var setUsdAmount = function(event) {
var amount = $('input[name=amount]').val();
var denomination = $('#token option:selected').text();
var estimate = getUSDEstimate(amount, denomination, function(estimate) {
$('#usd_amount').html(estimate);
});
};

// Wait until page is loaded, then run the function
$(document).ready(function() {
Expand Down
8 changes: 8 additions & 0 deletions app/assets/v2/js/shared.js
Expand Up @@ -679,3 +679,11 @@ $(document).ready(function() {
window.addEventListener('load', function() {
setInterval(listen_for_web3_changes, 300);
});

var setUsdAmount = function(event) {
var amount = $('input[name=amount]').val();
var denomination = $('#token option:selected').text();
var estimate = getUSDEstimate(amount, denomination, function(estimate) {
$('#usd_amount').html(estimate);
});
};
2 changes: 2 additions & 0 deletions app/dashboard/helpers.py
Expand Up @@ -508,6 +508,8 @@ def process_bounty_changes(old_bounty, new_bounty):
event_name = 'killed_bounty'
else:
event_name = 'work_done'
elif old_bounty.value_in_token < new_bounty.value_in_token:
event_name = 'increased_bounty'
else:
event_name = 'unknown_event'
logging.error(f'got an unknown event from bounty {old_bounty.pk} => {new_bounty.pk}: {json_diff}')
Expand Down
13 changes: 13 additions & 0 deletions app/dashboard/notifications.py
Expand Up @@ -91,6 +91,11 @@ def maybe_market_to_twitter(bounty, event_name):
"Hot off the blockchain! 🔥🔥🔥 There's a new task worth {} {} {} \n\n{}",
"💰 New Task Alert.. 💰 Earn {} {} {} for working on this 👇 \n\n{}",
]
if event_name == 'increased_bounty':
tweet_txts = tweet_txts + [
"Looking for paid Open Source work? Earn {} {} {} and boost your reputation by completing this task \n\n{}",
"Ding ding ding!! A bounty was just increased to {} {} {}! Someone must really want you to work on this task \n\n{}"
]

random.shuffle(tweet_txts)
tweet_txt = tweet_txts[0]
Expand Down Expand Up @@ -239,6 +244,14 @@ def build_github_notification(bounty, event_name, profile_pairs=None):
f"[here]({absolute_url})\n * Questions? Get help on the " \
f"<a href='https://gitcoin.co/slack'>Gitcoin Slack</a>\n * ${amount_open_work}" \
" more Funded OSS Work Available at: https://gitcoin.co/explorer\n"
if event_name == 'increased_bounty':
msg = f"__The funding of this issue was increased to {natural_value} " \
f"{bounty.token_name} {usdt_value}.__\n\n * If you would " \
f"like to work on this issue you can claim it [here]({absolute_url}).\n " \
"* If you've completed this issue and want to claim the bounty you can do so " \
f"[here]({absolute_url})\n * Questions? Get help on the " \
f"<a href='https://gitcoin.co/slack'>Gitcoin Slack</a>\n * ${amount_open_work}" \
" more Funded OSS Work Available at: https://gitcoin.co/explorer\n"
elif event_name == 'killed_bounty':
msg = f"__The funding of {natural_value} {bounty.token_name} " \
f"{usdt_value} attached to this issue has been **killed** by the bounty submitter__\n\n " \
Expand Down
109 changes: 109 additions & 0 deletions app/dashboard/templates/increase_bounty.html
@@ -0,0 +1,109 @@
{% comment %}
Copyright (C) 2017 Gitcoin Core

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

{% endcomment %}
{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
{% include 'shared/head.html' %}
{% include 'shared/cards.html' %}
</head>

<body class="interior {{active}}">
{% include 'shared/tag_manager_2.html' %}
<div class="container-fluid header dash">
{% include 'shared/nav.html' %}
</div>
<div class="row no-gutter">
<div class="col-12 body">
<div class="container-fluid no-gutter">
<div class="row font-smaller-2" id="form_container">
{% include 'shared/no_metamask_error.html' %}
{% include 'shared/zero_balance_error.html' %}
{% include 'shared/unlock_metamask.html' %}
<input type="hidden" name="standardBountiesId"
value="{{standard_bounties_id}}">
<input type="hidden" name="bountyOwnerAddress"
value="{{bounty_owner_address}}">
<input type="hidden" name="valueInToken"
value="{{value_in_token}}">
<div class="col offset-md-1 col-md-10 d-flex align-items-center justify-content-center" id="primary_form">
<div class="d-flex justify-content-center flex-column mt-5 w-50" id="primary_subform">
<div class="w-100" style="text-align: center;">
<h3>Increase Funding</h3>
<img src="{% static "v2/images/prime.png" %}">
</div>
{% include 'shared/network_status.html' %}
<div class="w-100">
<label class="form__label" for=issueURL>Issue URL</label>
<input name='issueURL' id="issueURL" class="w-100 form__input" type="url" placeholder="https://github.com/user/repo/pull/n" value="{% if issue_url %}{{issue_url}}{%endif%}" disabled/>
</div>
<div class="w-100 mt-2">
<label class="form__label" for="amount">Amount</label>
<div class="form__flex-group">
<div class="form__amount-wrapper">
<input name='amount' id='amount' class="form__input" type="number" placeholder="Amount" />
</div>
<div class="form__group-item">
<div class="form__select2">
<select name='deonomination' id='token' data-token-address='{{token_address}}' disabled></select>
</div>
<small class="form__input-help form__input-help--dynamic" id="usd_amount"></small>
</div>
</div>
</div>
<div class="w-100 mt-2 terms_container">
<div class="form__checkbox">
<input name='terms' id='terms' type="checkbox" value=1 />
<label class="form__label" for=terms>I have read, understand, and agree to, the <a href="{% url "terms" %}" target=new>Terms of Service</a>:</label>
</div>
</div>
{% include 'shared/metamask_estimate.html' %}
<div class="form__footer form__footer--right">
<button class="button button--primary" type="submit" id="submitBounty">Increase Funding</button>
<br />
<a target=new href="https://github.com/gitcoinco/gitcoinco/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp">Having trouble? Click here.</a>
</div>
{% include 'shared/newsletter.html' %}
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'shared/bottom_notification.html' %}
{% include 'shared/analytics.html' %}
{% include 'shared/footer_scripts.html' %}
{% include 'shared/rollbar.html' %}
{% include 'shared/footer.html' %}

</body>


<!-- jQuery -->
<script src="{% static "v2/js/ipfs-api.js" %}"></script>
<script src="{% static "v2/js/ipfs.js" %}"></script>
<script src="{% static "v2/js/amounts.js" %}"></script>
<script src="{% static "v2/js/abi.js" %}"></script>
<script src="{% static "v2/js/tokens.js" %}"></script>
<script src="{% static "v2/js/shared.js" %}"></script>
<script src="{% static "v2/js/pages/shared_bounty_mutation_estimate_gas.js" %}"></script>
<script src="{% static "v2/js/pages/increase_bounty.js" %}"></script>


</html>
8 changes: 8 additions & 0 deletions app/dashboard/tests/test_notifications.py
Expand Up @@ -69,6 +69,14 @@ def test_build_github_notification_killed_bounty(self):
assert 'Questions?' in message
assert f'${self.amount_open_work}' in message

def test_build_github_notification_increased_bounty(self):
"""Test the dashboard helper build_github_notification method with new_bounty."""
message = build_github_notification(self.bounty, 'increased_bounty')
assert message.startswith(f'__The funding of this issue was increased to {self.natural_value} {self.bounty.token_name}')
assert self.usdt_value in message
assert f'[here]({self.absolute_url})' in message
assert f'${self.amount_open_work}' in message

def tearDown(self):
"""Perform cleanup for the testcase."""
self.bounty.delete()

0 comments on commit 90f0883

Please sign in to comment.