-
-
Notifications
You must be signed in to change notification settings - Fork 775
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
WIP: Stats email v2 #1311
WIP: Stats email v2 #1311
Changes from 11 commits
8fb9fce
7054f4e
5c28eb7
18cbe4d
fcf06c2
7a59d39
55c00e2
0ae0190
9a15407
8e281a0
509f82f
42ddb96
e60686b
491d09d
bff6864
5e8c0cc
083d5ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1127,22 +1127,97 @@ def get_quarterly_stats(self): | |
dict : containing the following information | ||
'user_total_earned_eth': Total earnings of user in ETH. | ||
'user_total_earned_usd': Total earnings of user in USD. | ||
'user_fulfilled_bounties_count': Total bounties fulfilled by user. | ||
'user_total_funded_usd': Total value of bounties funded by the user on bounties in done status in USD | ||
'user_total_funded_hours': Total hours input by the developers on the fulfillment of bounties created by the user in USD | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (132 > 120 characters) |
||
'user_fulfilled_bounties_count': Total bounties fulfilled by user | ||
'user_fufilled_bounties': bool, if the user fulfilled bounties | ||
'user_funded_bounties_count': Total bounties funded by the user | ||
'user_funded_bounties': bool, if the user funded bounties in the last quarter | ||
'user_funded_bounty_developers': Unique set of users that fulfilled bounties funded by the user | ||
'user_avg_hours_per_funded_bounty': Average hours input by developer on fulfillment per bounty | ||
'user_avg_hourly_rate_per_funded_bounty': Average hourly rate in dollars per bounty funded by user | ||
'user_avg_eth_earned_per_bounty': Average earning in ETH earned by user per bounty | ||
'user_avg_usd_earned_per_bounty': Average earning in USD earned by user per bounty | ||
'user_num_completed_bounties': Total no. of bounties completed. | ||
'user_num_funded_fulfilled_bounties': Total bounites that were funded by the user and fulfilled | ||
'user_bounty_completion_percentage': Percentage of bounties successfully completed by the user | ||
'user_funded_fulfilled_percentage': Percentage of bounties funded by the user that were fulfilled | ||
'user_active_in_last_quarter': bool, if the user was active in last quarter | ||
'user_no_of_languages': No of languages user used while working on bounties. | ||
'user_languages': Languages that were used in bounties that were worked on. | ||
'relevant_bounties': a list of Bounty(s) that would match the skillset input by the user into the Match tab of their settings | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no recommendation on https://www.python.org/dev/peps/pep-0257/, could you advise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (137 > 120 characters) |
||
""" | ||
user_active_in_last_quarter = False | ||
user_fulfilled_bounties = False | ||
user_funded_bounties = False | ||
last_quarter = datetime.now() - timedelta(days=90) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F821 undefined name 'timedelta' |
||
bounties = self.bounties.filter(modified_on__gte=last_quarter) | ||
fulfilled_bounties = [ | ||
bounty for bounty in bounties if bounty.is_hunter(self.handle) and bounty.status == 'done' | ||
] | ||
fulfilled_bounties_count = len(fulfilled_bounties) | ||
funded_bounties = [ | ||
bounty for bounty in bounties if bounty.is_funder(self.handle) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is any of this data STUBBed out? are you unsure of any of the calcs you did? if so, mind throwing a few TODOs in there for us to work through? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added TODOs |
||
] | ||
funded_bounties_count = len(funded_bounties) | ||
if funded_bounties_count: | ||
total_funded_usd = sum([ | ||
bounty.value_in_usdt if bounty.value_in_usdt else 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use Django Aggregation here. |
||
for bounty in funded_bounties | ||
]) | ||
total_funded_hourly_rate = float(0) | ||
hourly_rate_bounties_counted = float(0) | ||
for bounty in funded_bounties: | ||
hourly_rate = bounty.hourly_rate | ||
if hourly_rate: | ||
total_funded_hourly_rate += bounty.hourly_rate | ||
hourly_rate_bounties_counted += 1 | ||
funded_bounty_fulfillments = [] | ||
for bounty in funded_bounties: | ||
fulfillments = bounty.fulfillments.filter(accepted=True) | ||
for fulfillment in fulfillments: | ||
if isinstance(fulfillment, BountyFulfillment): | ||
funded_bounty_fulfillments.append(fulfillment) | ||
funded_bounty_fulfillments_count = len(funded_bounty_fulfillments) | ||
|
||
total_funded_hours = 0 | ||
funded_fulfillments_with_hours_counted = 0 | ||
if funded_bounty_fulfillments_count: | ||
from decimal import Decimal | ||
for fulfillment in funded_bounty_fulfillments: | ||
if isinstance(fulfillment.fulfiller_hours_worked, Decimal): | ||
total_funded_hours += fulfillment.fulfiller_hours_worked | ||
funded_fulfillments_with_hours_counted += 1 | ||
|
||
user_funded_bounty_developers = [] | ||
for fulfillment in funded_bounty_fulfillments: | ||
user_funded_bounty_developers.append(fulfillment.fulfiller_github_username.lstrip('@')) | ||
user_funded_bounty_developers = [*{*user_funded_bounty_developers}] | ||
if funded_fulfillments_with_hours_counted: | ||
avg_hourly_rate_per_funded_bounty = float(total_funded_hourly_rate) / float(funded_fulfillments_with_hours_counted) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (131 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (131 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (131 > 120 characters) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E501 line too long (131 > 120 characters) |
||
avg_hours_per_funded_bounty = float(total_funded_hours) / float(funded_fulfillments_with_hours_counted) | ||
else: | ||
avg_hourly_rate_per_funded_bounty = 0 | ||
avg_hours_per_funded_bounty = 0 | ||
funded_fulfilled_bounties = [ | ||
bounty for bounty in funded_bounties if bounty.status == 'done' | ||
] | ||
num_funded_fulfilled_bounties = len(funded_fulfilled_bounties) | ||
funded_fulfilled_percent = float( | ||
# Round to 0 places of decimals to be displayed in template | ||
round(num_funded_fulfilled_bounties * 1.0 / funded_bounties_count, 2) * 100 | ||
) | ||
user_funded_bounties = True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F841 local variable 'user_funded_bounties' is assigned to but never used |
||
else: | ||
num_funded_fulfilled_bounties = 0 | ||
funded_fulfilled_percent = 0 | ||
user_funded_bounties = False | ||
avg_hourly_rate_per_funded_bounty = 0 | ||
avg_hours_per_funded_bounty = 0 | ||
total_funded_usd = 0 | ||
total_funded_hours = 0 | ||
user_funded_bounty_developers = [] | ||
|
||
total_earned_eth = sum([ | ||
bounty.value_in_eth if bounty.value_in_eth else 0 | ||
for bounty in fulfilled_bounties | ||
|
@@ -1165,13 +1240,40 @@ def get_quarterly_stats(self): | |
if fulfilled_bounties_count: | ||
avg_eth_earned_per_bounty = total_earned_eth / fulfilled_bounties_count | ||
avg_usd_earned_per_bounty = total_earned_usd / fulfilled_bounties_count | ||
user_fulfilled_bounties = True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F841 local variable 'user_fulfilled_bounties' is assigned to but never used |
||
|
||
user_languages = [] | ||
for bounty in fulfilled_bounties: | ||
user_languages += bounty.keywords.split(',') | ||
user_languages = set(user_languages) | ||
user_no_of_languages = len(user_languages) | ||
|
||
if num_completed_bounties or fulfilled_bounties_count: | ||
user_active_in_last_quarter = True | ||
|
||
relevant_bounties = [] | ||
else: | ||
from marketing.utils import get_or_save_email_subscriber | ||
user_coding_languages = get_or_save_email_subscriber(self.email, 'internal').keywords | ||
if user_coding_languages is not None: | ||
potential_bounties = Bounty.objects.all() | ||
relevant_bounties = Bounty.objects.none() | ||
for keyword in user_coding_languages: | ||
relevant_bounties = relevant_bounties.union(potential_bounties.filter( | ||
network=Profile.get_network(), | ||
current_bounty=True, | ||
metadata__icontains=keyword, | ||
idx_status__in=['open'], | ||
).order_by('?') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. W291 trailing whitespace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. W291 trailing whitespace |
||
) | ||
relevant_bounties = relevant_bounties[:3] | ||
relevant_bounties = list(relevant_bounties) | ||
# Round to 2 places of decimals to be diplayed in templates | ||
completetion_percent = float('%.2f' % completetion_percent) | ||
funded_fulfilled_percent = float('%.2f' % funded_fulfilled_percent) | ||
avg_eth_earned_per_bounty = float('%.2f' % avg_eth_earned_per_bounty) | ||
avg_usd_earned_per_bounty = float('%.2f' % avg_usd_earned_per_bounty) | ||
avg_hourly_rate_per_funded_bounty = float('%.2f' % avg_hourly_rate_per_funded_bounty) | ||
avg_hours_per_funded_bounty = float('%.2f' % avg_hours_per_funded_bounty) | ||
total_earned_eth = float('%.2f' % total_earned_eth) | ||
total_earned_usd = float('%.2f' % total_earned_usd) | ||
|
||
|
@@ -1184,14 +1286,25 @@ def get_quarterly_stats(self): | |
return { | ||
'user_total_earned_eth': total_earned_eth, | ||
'user_total_earned_usd': total_earned_usd, | ||
'user_total_funded_usd': total_funded_usd, | ||
'user_total_funded_hours': total_funded_hours, | ||
'user_fulfilled_bounties_count': fulfilled_bounties_count, | ||
'user_fulfilled_bounties': user_fulfilled_bounties, | ||
'user_funded_bounties_count': funded_bounties_count, | ||
'user_funded_bounties': user_funded_bounties, | ||
'user_funded_bounty_developers': user_funded_bounty_developers, | ||
'user_avg_hours_per_funded_bounty': avg_hours_per_funded_bounty, | ||
'user_avg_hourly_rate_per_funded_bounty': avg_hourly_rate_per_funded_bounty, | ||
'user_avg_eth_earned_per_bounty': avg_eth_earned_per_bounty, | ||
'user_avg_usd_earned_per_bounty': avg_usd_earned_per_bounty, | ||
'user_num_completed_bounties': num_completed_bounties, | ||
'user_num_funded_fulfilled_bounties': num_funded_fulfilled_bounties, | ||
'user_bounty_completion_percentage': completetion_percent, | ||
'user_funded_fulfilled_percentage': funded_fulfilled_percent, | ||
'user_active_in_last_quarter': user_active_in_last_quarter, | ||
'user_no_of_languages': user_no_of_languages, | ||
'user_languages': user_languages | ||
'user_languages': user_languages, | ||
'relevant_bounties': relevant_bounties | ||
} | ||
|
||
@property | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -199,6 +199,7 @@ | |
<h1>GITCOIN QUARTERLY</h1> | ||
|
||
{% if user_active_in_last_quarter %} | ||
{% if user_fulfilled_bounties %} | ||
<h2>You've Been BUILDing</h2> | ||
<h4>An overview of your stats from the last 3 months.</h4> | ||
<div class="centered-contents stat-box row"> | ||
|
@@ -237,8 +238,54 @@ <h4>An overview of your stats from the last 3 months.</h4> | |
<div class="button-action"> | ||
<a class="button" href="{% url 'dashboard' %}">{% trans "Share my stats" %}</a> | ||
</div> | ||
{% endif %} | ||
{% endif %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This indent doesn't match the starting |
||
|
||
{% if user_funded_bounties_count %} | ||
<h2>You've Been Making Money Moves</h2> | ||
<h4>An overview of your OSS project's stats from the last 3 months.</h4> | ||
<div class="centered-contents stat-box row"> | ||
<div class="stat-img full-bordered-circle percent-content col-sm-12"> | ||
<p class="content">{{ user_funded_fulfilled_percentage}} %</p> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we switch this to: |
||
</div> | ||
<div class="stat-contents col-sm-12"> | ||
<p>Of the {{ user_funded_bounties_count | intcomma }} issue{%if user_funded_bounties_count > 1 %}s{% endif %} that you funded a bounty for, {{ user_num_funded_fulfilled_bounties | intcomma }} were fulfilled -- that's a {{ user_funded_fulfilled_percentage}}% success rate!</p> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use django's template tag |
||
</div> | ||
</div> | ||
<div class="centered-contents stat-box row"> | ||
<div class="stat-img total-transaction"> | ||
<p class="content">{{ user_funded_bounty_developers | length }} contributors</p> | ||
</div> | ||
<div class="stat-contents col-sm-12"> | ||
<p>The following {{ user_funded_bounty_developers | length }} developers have made contributions to your OSS projects this quarter: | ||
{% for developer in user_funded_bounty_developers %}{% if forloop.counter == user_num_funded_bounty_developers %} & {% elif forloop.first %}{% else %}, {% endif %}<a href="https://github.com/{{ developer }}">@{{ developer }}</a>{% endfor %}</p> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we drop these fors and ifs to their own lines for readability? |
||
</div> | ||
</div> | ||
{% if user_total_funded_hours %} | ||
<div class="centered-contents stat-box row"> | ||
<div class="stat-img"> | ||
<p class="content bounty-img">{{ user_total_funded_hours | intcomma }} hours</p> | ||
<p class="content bounty-fund-img">{{ user_total_funded_usd | intcomma }} USD</p> | ||
</div> | ||
<div class="stat-contents"> | ||
<p>Developers worked an average of {{ user_avg_hours_per_funded_bounty }} hours per issue on your projects (only applies to bounties where users entered their hours). You funded a total of ${{ user_total_funded_usd | intcomma }} USD of work at an average rate of ${{ user_avg_hourly_rate_per_funded_bounty }} USD / hour..</p> | ||
</div> | ||
</div> | ||
{% endif %} | ||
|
||
<div class="button-action"> | ||
<a class="button" href="{% url 'dashboard' %}">{% trans "Share my stats" %}</a> | ||
</div> | ||
{% endif %} | ||
{% endif %} | ||
{% if not user_active_last_quarter and relevant_bounties %} | ||
<h2>You could have been BUILDing</h2> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and remaining strings throughout this file need wrapped in |
||
{% if relevant_bounties|length == 1 %} This bounty {% endif %}{% if relevant_bounties|length > 1 %} These bounties {% endif %} from the issue explorer match your skillset: | ||
<br> | ||
{% for bounty in relevant_bounties %} | ||
<br> | ||
{% include 'emails/bounty.html' with bounty=bounty %} | ||
{% endfor %} | ||
{% endif %} | ||
<hr> | ||
|
||
<h2>Platform-wide Stats</h2> | ||
|
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.
E501 line too long (132 > 120 characters)