Skip to content

Commit

Permalink
Added links for user-profile stats.
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahboyce committed Nov 15, 2024
1 parent a0c4bf3 commit 51d5a45
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 40 deletions.
134 changes: 122 additions & 12 deletions accounts/tests.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,130 @@
from unittest import mock

from django.contrib.auth.models import User
from django.test import TestCase
from django.test import TestCase, override_settings
from django_hosts.resolvers import reverse

from tracdb.models import Revision, Ticket, TicketChange
from tracdb.testutils import TracDBCreateDatabaseMixin


class UserProfileTests(TracDBCreateDatabaseMixin, TestCase):
databases = {"default", "trac"}

@classmethod
def setUpTestData(cls):
cls.credentials = {"username": "a-user", "password": "password"}
cls.user = User.objects.create_user(**cls.credentials)
ticket_1 = Ticket.objects.create(
status="closed", reporter="a-user", owner="a-user", resolution="fixed"
)
ticket_2 = Ticket.objects.create(
status="closed", reporter="b-user", owner="a-user", resolution="fixed"
)
ticket_3 = Ticket.objects.create(
status="closed", reporter="b-user", owner="b-user", resolution="fixed"
)
Ticket.objects.create(
status="closed", reporter="b-user", owner="a-user", resolution="wontfix"
)
Ticket.objects.create(status="new", reporter="a-user")
Ticket.objects.create(status="new", reporter="b-user")
Revision.objects.create(
author="a-user",
rev="91c879eda595c12477bbfa6f51115e88b75ddf88",
_time=1731669560,
)
Revision.objects.create(
author="a-user",
rev="da2432cccae841f0d7629f17a5d79ec47ed7b7cb",
_time=1731669560,
)
Revision.objects.create(
author="b-user",
rev="63dbe30d3363715deaf280214d75b03f6d65a571",
_time=1731669560,
)
TicketChange.objects.create(
author="a-user",
field="stage",
oldvalue="Unreviewed",
newvalue="Accepted",
ticket=ticket_1,
_time=1731669560,
)
TicketChange.objects.create(
author="b-user",
field="stage",
oldvalue="Unreviewed",
newvalue="Accepted",
ticket=ticket_2,
_time=1731669560,
)
TicketChange.objects.create(
author="a-user",
field="owner",
oldvalue="b-user",
newvalue="a-user",
ticket=ticket_3,
_time=1731669560,
)

@override_settings(TRAC_URL="https://code.djangoproject.com/")
def test_user_profile(self):
a_user_response = self.client.get(
reverse("user_profile", host="www", args=["a-user"])
)
b_user_response = self.client.get(
reverse("user_profile", host="www", args=["b-user"])
)
self.assertContains(a_user_response, "a-user")
self.assertContains(b_user_response, "b-user")
self.assertContains(
a_user_response,
'<a href="https://github.com/django/django/commits/main/'
'?author=a-user">Commits: 2</a>',
html=True,
)
self.assertContains(
b_user_response,
'<a href="https://github.com/django/django/commits/main/'
'?author=b-user">Commits: 1</a>',
html=True,
)
self.assertContains(
a_user_response,
'<a href="https://code.djangoproject.com/query?'
'owner=~a-user&resolution=fixed&desc=1&order=changetime">'
"Tickets fixed: 2</a>",
html=True,
)
self.assertContains(
a_user_response,
'<a href="https://code.djangoproject.com/query?'
'owner=~b-user&resolution=fixed&desc=1&order=changetime">'
"Tickets fixed: 1</a>",
html=True,
)
self.assertContains(
a_user_response,
'<a href="https://code.djangoproject.com/query?'
'reporter=~a-user&desc=1&order=changetime">'
"Tickets opened: 2</a>",
html=True,
)
self.assertContains(
b_user_response,
'<a href="https://code.djangoproject.com/query?'
'reporter=~b-user&desc=1&order=changetime">'
"Tickets opened: 4</a>",
html=True,
)


class ViewTests(TestCase):
def setUp(self):
self.credentials = {"username": "a-user", "password": "password"}
self.user = User.objects.create_user(**self.credentials)
class ViewsTests(TestCase):

@mock.patch("accounts.views.get_user_stats")
def test_user_profile(self, mock_user_stats):
response = self.client.get(reverse("user_profile", host="www", args=["a-user"]))
self.assertContains(response, "a-user")
mock_user_stats.assert_called_once_with(self.user)
@classmethod
def setUpTestData(cls):
cls.credentials = {"username": "a-user", "password": "password"}
cls.user = User.objects.create_user(**cls.credentials)

def test_login_redirect(self):
response = self.client.post(reverse("login"), self.credentials)
Expand Down
6 changes: 5 additions & 1 deletion djangoproject/templates/accounts/user_profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ <h1>
<h2>{% translate "Lies, damned lies, and statistics:" %}</h2>
<ul>
{% for stat, value in stats.items %}
<li>{{ stat }}: {{ value|intcomma }}.</li>
<li>
{% if value.link %}<a href="{{ value.link }}">{% endif %}
{{ stat }}: {{ value.count|intcomma }}.
{% if value.link %}</a>{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
Expand Down
52 changes: 25 additions & 27 deletions tracdb/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
"""

import operator
from collections import OrderedDict
from collections import OrderedDict, namedtuple

import django.db
from django.conf import settings

from .models import Attachment, Revision, Ticket, TicketChange
from .models import Revision, Ticket, TicketChange

_statfuncs = []


StatData = namedtuple("StatData", ["count", "link"])


def get_trac_link(query):
return f"{settings.TRAC_URL}query?{query}&desc=1&order=changetime"


def stat(title):
"""
Register a function as a "stat"
Expand All @@ -36,42 +43,33 @@ def get_user_stats(username):

@stat("Commits")
def commit_count(username):
return Revision.objects.filter(author=username).count()
count = Revision.objects.filter(author=username).count()
# This assumes that the username is their GitHub username, this is very
# often the case. If this is incorrect, the GitHub will show no commits.
link = f"https://github.com/django/django/commits/main/?author={username}"
return StatData(count=count, link=link)


@stat("Tickets closed")
def tickets_closed(username):
# Raw query so that we can do COUNT(DISTINCT ticket).
q = """SELECT COUNT(DISTINCT ticket) FROM ticket_change
WHERE author = %s AND field = 'status' AND newvalue = 'closed';"""
return run_single_value_query(q, username)
@stat("Tickets fixed")
def tickets_fixed(username):
count = Ticket.objects.filter(owner=username, resolution="fixed").count()
link = get_trac_link(f"owner=~{username}&resolution=fixed")
return StatData(count=count, link=link)


@stat("Tickets opened")
def tickets_opened(username):
return Ticket.objects.filter(reporter=username).count()
count = Ticket.objects.filter(reporter=username).count()
link = get_trac_link(f"reporter=~{username}")
return StatData(count=count, link=link)


@stat("New tickets reviewed")
@stat("New tickets triaged")
def new_tickets_reviewed(username):
# We don't want to de-dup as for tickets_closed: multiple reviews of the
# same ticket should "count" as a review.
qs = TicketChange.objects.filter(
author=username, field="stage", oldvalue="Unreviewed"
)
qs = qs.exclude(newvalue="Unreviewed")
return qs.count()


@stat("Patches submitted")
def patches_submitted(username):
return Attachment.objects.filter(author=username).count()


def run_single_value_query(query, *params):
"""
Helper: run a query returning a single value (e.g. a COUNT) and return the value.
"""
c = django.db.connections["trac"].cursor()
c.execute(query, params)
return c.fetchone()[0]
return StatData(count=qs.count(), link=None)

0 comments on commit 51d5a45

Please sign in to comment.