Permalink
Browse files

Add annotation count to admin user info page

This commit adds a total (public + shared + groups) annotation count to
the user information table displayed in the admin interface. This can be
useful when determining if a user is actively using Hypothesis, or to
correlate with the public API when determining if a user has mistakenly
make private annotations.
  • Loading branch information...
nickstenning committed Jan 6, 2016
1 parent 38e57f8 commit 4d13b52a38f57fba4a34608570d0e530e692f1e8
Showing with 71 additions and 23 deletions.
  1. +21 −1 h/admin.py
  2. +4 −0 h/templates/admin/users.html.jinja2
  3. +46 −22 h/test/admin_test.py
View
@@ -168,12 +168,22 @@ def staff_remove(request):
permission='admin_users')
def users_index(request):
user = None
user_meta = {}
username = request.params.get('username')
if username is not None:
user = models.User.get_by_username(username)
return {'username': username, 'user': user}
if user is not None:
# Fetch information on how many annotations the user has created
userid = util.userid_from_username(username, request)
query = _all_user_annotations_query(userid)
result = request.es.conn.count(index=request.es.index,
doc_type='annotation',
body={'query': query})
user_meta['annotations_count'] = result['count']
return {'username': username, 'user': user, 'user_meta': user_meta}
@view.view_config(route_name='admin_badge',
@@ -239,6 +249,16 @@ def groups_index_csv(request):
return {'header': header, 'rows': rows}
def _all_user_annotations_query(userid):
"""Query matching all annotations (shared and private) owned by userid."""
return {
'filtered': {
'filter': {'term': {'user': userid}},
'query': {'match_all': {}}
}
}
def includeme(config):
config.add_route('admin_index', '/admin')
config.add_route('admin_features', '/admin/features')
@@ -37,6 +37,10 @@
<th>Is staff?</th>
<td>{% if user.staff %}&#x2714;{% else %}&#x2718;{% endif %}</td>
</tr>
<tr>
<th>Annotations</th>
<td>{{ user_meta['annotations_count'] }}</td>
</tr>
</tbody>
</table>
{% else %}
View
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
from mock import ANY
from mock import MagicMock
from mock import Mock
from mock import patch
import pytest
from pyramid.testing import DummyRequest
from pyramid.testing import DummyRequest as _DummyRequest
from pyramid import httpexceptions
from h import accounts
@@ -18,6 +20,12 @@ def __init__(self, name):
self.staff = False
class DummyRequest(_DummyRequest):
def __init__(self, *args, **kwargs):
kwargs.setdefault('auth_domain', 'example.com')
super(DummyRequest, self).__init__(*args, **kwargs)
features_save_fixtures = pytest.mark.usefixtures('Feature',
'check_csrf_token',
'routes_mapper')
@@ -106,7 +114,7 @@ def test_nipsa_index_with_multiple_nipsad_users(nipsa):
@nipsa_add_fixtures
def test_nipsa_add_calls_nipsa_api_with_userid(nipsa):
request = DummyRequest(params={"add": "kiki"}, auth_domain='example.com')
request = DummyRequest(params={"add": "kiki"})
admin.nipsa_add(request)
@@ -116,40 +124,33 @@ def test_nipsa_add_calls_nipsa_api_with_userid(nipsa):
@nipsa_add_fixtures
def test_nipsa_add_returns_index(nipsa_index):
request = DummyRequest(params={"add": "kiki"}, auth_domain='example.com')
request = DummyRequest(params={"add": "kiki"})
nipsa_index.return_value = "Keine Bange!"
assert admin.nipsa_add(request) == "Keine Bange!"
# The fixtures required to mock all of nipsa_remove()'s dependencies.
nipsa_remove_fixtures = pytest.mark.usefixtures('nipsa')
nipsa_remove_fixtures = pytest.mark.usefixtures('nipsa', 'routes_mapper')
@nipsa_remove_fixtures
def test_nipsa_remove_calls_nipsa_api_with_userid(nipsa):
request = Mock(
params={"remove": "kiki"},
auth_domain="hypothes.is",
registry=Mock(settings={})
)
request = DummyRequest(params={"remove": "kiki"})
admin.nipsa_remove(request)
nipsa.remove_nipsa.assert_called_once_with(
request, "acct:kiki@hypothes.is")
request, "acct:kiki@example.com")
@nipsa_remove_fixtures
def test_nipsa_remove_redirects_to_index():
request = Mock(params={"remove": "kiki"},
domain="hypothes.is",
route_url=Mock(return_value="/nipsa"))
request = DummyRequest(params={"remove": "kiki"})
response = admin.nipsa_remove(request)
assert isinstance(response, httpexceptions.HTTPSeeOther)
assert response.location == "/nipsa"
# The fixtures required to mock all of admins_index()'s dependencies.
@@ -437,36 +438,59 @@ def test_users_index():
result = admin.users_index(request)
assert result == {"username": None, "user": None}
assert result == {'username': None, 'user': None, 'user_meta': {}}
@users_index_fixtures
def test_users_index_looks_up_users_by_username(User):
request = DummyRequest(params={"username": "bob"})
es = MagicMock()
request = DummyRequest(params={"username": "bob"},
es=es)
result = admin.users_index(request)
admin.users_index(request)
User.get_by_username.assert_called_with("bob")
@users_index_fixtures
def test_users_index_queries_annotation_count(User):
es = MagicMock()
request = DummyRequest(params={"username": "bob"},
es=es)
admin.users_index(request)
es.conn.count.assert_called_with(index=es.index,
doc_type='annotation',
body=ANY)
@users_index_fixtures
def test_users_index_no_user_found(User):
request = DummyRequest(params={"username": "bob"})
es = MagicMock()
request = DummyRequest(params={"username": "bob"},
es=es)
User.get_by_username.return_value = None
result = admin.users_index(request)
assert result == {"username": "bob", "user": None}
assert result == {'username': "bob", 'user': None, 'user_meta': {}}
@users_index_fixtures
def test_users_index_user_found(User):
request = DummyRequest(params={"username": "bob"})
es = MagicMock()
request = DummyRequest(params={"username": "bob"},
es=es)
es.conn.count.return_value = {'count': 43}
result = admin.users_index(request)
assert result == {"username": "bob",
"user": User.get_by_username.return_value}
assert result == {
'username': "bob",
'user': User.get_by_username.return_value,
'user_meta': {'annotations_count': 43},
}
badge_index_fixtures = pytest.mark.usefixtures('models')

0 comments on commit 4d13b52

Please sign in to comment.