mirror of https://github.com/zulip/zulip.git
analytics: Make stats of all realms accessible to server admins.
In this commit: Two new URLs are added, to make all realms accessible for server admins. One is for the stats page itself and another for getting chart data i.e. chart data API requests. For the above two new URLs corresponding two view functions are added.
This commit is contained in:
parent
40dc48a033
commit
b26c38bc47
|
@ -24,6 +24,24 @@ class TestStatsEndpoint(ZulipTestCase):
|
|||
# Check that we get something back
|
||||
self.assert_in_response("Zulip analytics for", result)
|
||||
|
||||
def test_stats_for_realm(self) -> None:
|
||||
user_profile = self.example_user('hamlet')
|
||||
self.login(user_profile.email)
|
||||
|
||||
result = self.client_get('/stats/realm/zulip/')
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
user_profile = self.example_user('hamlet')
|
||||
user_profile.is_staff = True
|
||||
user_profile.save(update_fields=['is_staff'])
|
||||
|
||||
result = self.client_get('/stats/realm/not_existing_realm/')
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
result = self.client_get('/stats/realm/zulip/')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assert_in_response("Zulip analytics for", result)
|
||||
|
||||
class TestGetChartData(ZulipTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.realm = get_realm('zulip')
|
||||
|
@ -233,6 +251,28 @@ class TestGetChartData(ZulipTestCase):
|
|||
{'chart_name': 'number_of_humans'})
|
||||
self.assert_json_error_contains(result, 'No analytics data available')
|
||||
|
||||
def test_get_chart_data_for_realm(self) -> None:
|
||||
user_profile = self.example_user('hamlet')
|
||||
self.login(user_profile.email)
|
||||
|
||||
result = self.client_get('/json/analytics/chart_data/realm/zulip/',
|
||||
{'chart_name': 'number_of_humans'})
|
||||
self.assert_json_error(result, "Must be an server administrator", 400)
|
||||
|
||||
user_profile = self.example_user('hamlet')
|
||||
user_profile.is_staff = True
|
||||
user_profile.save(update_fields=['is_staff'])
|
||||
stat = COUNT_STATS['realm_active_humans::day']
|
||||
self.insert_data(stat, [None], [])
|
||||
|
||||
result = self.client_get('/json/analytics/chart_data/realm/not_existing_realm',
|
||||
{'chart_name': 'number_of_humans'})
|
||||
self.assert_json_error(result, 'Invalid organization', 400)
|
||||
|
||||
result = self.client_get('/json/analytics/chart_data/realm/zulip',
|
||||
{'chart_name': 'number_of_humans'})
|
||||
self.assert_json_success(result)
|
||||
|
||||
class TestGetChartDataHelpers(ZulipTestCase):
|
||||
# last_successful_fill is in analytics/models.py, but get_chart_data is
|
||||
# the only function that uses it at the moment
|
||||
|
|
|
@ -11,6 +11,8 @@ i18n_urlpatterns = [
|
|||
name='analytics.views.get_realm_activity'),
|
||||
url(r'^user_activity/(?P<email>[\S]+)/$', analytics.views.get_user_activity,
|
||||
name='analytics.views.get_user_activity'),
|
||||
url(r'^stats/realm/(?P<realm_str>[\S]+)/$', analytics.views.stats_for_realm,
|
||||
name='analytics.views.stats_for_realm'),
|
||||
|
||||
# User-visible stats page
|
||||
url(r'^stats$', analytics.views.stats,
|
||||
|
@ -29,6 +31,8 @@ v1_api_and_json_patterns = [
|
|||
# get data for the graphs at /stats
|
||||
url(r'^analytics/chart_data$', rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data'}),
|
||||
url(r'^analytics/chart_data/realm/(?P<realm_str>[\S]+)$', rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_realm'}),
|
||||
]
|
||||
|
||||
i18n_urlpatterns += [
|
||||
|
|
|
@ -26,9 +26,10 @@ from analytics.lib.counts import COUNT_STATS, CountStat, process_count_stat
|
|||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import BaseCount, InstallationCount, \
|
||||
RealmCount, StreamCount, UserCount, last_successful_fill
|
||||
from zerver.decorator import require_server_admin, \
|
||||
from zerver.decorator import require_server_admin, require_server_admin_api, \
|
||||
to_non_negative_int, to_utc_datetime, zulip_login_required
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.json_encoder_for_html import JSONEncoderForHTML
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import ceiling_to_day, \
|
||||
|
@ -36,17 +37,48 @@ from zerver.lib.timestamp import ceiling_to_day, \
|
|||
from zerver.models import Client, get_realm, Realm, \
|
||||
UserActivity, UserActivityInterval, UserProfile
|
||||
|
||||
@zulip_login_required
|
||||
def stats(request: HttpRequest) -> HttpResponse:
|
||||
def render_stats(request: HttpRequest, realm: Realm) -> HttpRequest:
|
||||
page_params = dict(
|
||||
is_staff = request.user.is_staff,
|
||||
stats_realm = realm.string_id,
|
||||
debug_mode = False,
|
||||
)
|
||||
|
||||
return render(request,
|
||||
'analytics/stats.html',
|
||||
context=dict(realm_name = request.user.realm.name))
|
||||
context=dict(target_realm_name=realm.name,
|
||||
page_params=JSONEncoderForHTML().encode(page_params)))
|
||||
|
||||
@zulip_login_required
|
||||
def stats(request: HttpRequest) -> HttpResponse:
|
||||
realm = request.user.realm
|
||||
return render_stats(request, realm)
|
||||
|
||||
@require_server_admin
|
||||
@has_request_variables
|
||||
def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
|
||||
realm = get_realm(realm_str)
|
||||
if realm is None:
|
||||
return HttpResponseNotFound("Realm %s does not exist" % (realm_str,))
|
||||
|
||||
return render_stats(request, realm)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_realm(request: HttpRequest, user_profile: UserProfile,
|
||||
realm_str: str, **kwargs: Any) -> HttpResponse:
|
||||
realm = get_realm(realm_str)
|
||||
if realm is None:
|
||||
raise JsonableError(_("Invalid organization"))
|
||||
|
||||
return get_chart_data(request=request, user_profile=user_profile, realm=realm, **kwargs)
|
||||
|
||||
@has_request_variables
|
||||
def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name: Text=REQ(),
|
||||
min_length: Optional[int]=REQ(converter=to_non_negative_int, default=None),
|
||||
start: Optional[datetime]=REQ(converter=to_utc_datetime, default=None),
|
||||
end: Optional[datetime]=REQ(converter=to_utc_datetime, default=None)) -> HttpResponse:
|
||||
end: Optional[datetime]=REQ(converter=to_utc_datetime, default=None),
|
||||
realm: Optional[Realm]=None) -> HttpResponse:
|
||||
if chart_name == 'number_of_humans':
|
||||
stat = COUNT_STATS['realm_active_humans::day']
|
||||
tables = [RealmCount]
|
||||
|
@ -88,7 +120,8 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
|
|||
raise JsonableError(_("Start time is later than end time. Start: %(start)s, End: %(end)s") %
|
||||
{'start': start, 'end': end})
|
||||
|
||||
realm = user_profile.realm
|
||||
if realm is None:
|
||||
realm = user_profile.realm
|
||||
if start is None:
|
||||
start = realm.date_created
|
||||
if end is None:
|
||||
|
|
|
@ -72,9 +72,17 @@ $(function () {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
function get_chart_data(data, callback) {
|
||||
var url;
|
||||
if (page_params.is_staff) {
|
||||
url = '/json/analytics/chart_data/realm/' + page_params.stats_realm;
|
||||
} else {
|
||||
url = '/json/analytics/chart_data';
|
||||
}
|
||||
|
||||
$.get({
|
||||
url: '/json/analytics/chart_data',
|
||||
url: url,
|
||||
data: data,
|
||||
idempotent: true,
|
||||
success: function (data) {
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
{% extends "zerver/base.html" %}
|
||||
|
||||
{% block page_params %}
|
||||
<script type="text/javascript">
|
||||
var page_params = {{ page_params|safe }};
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block customhead %}
|
||||
{% stylesheet 'portico' %}
|
||||
{% endblock %}
|
||||
|
@ -14,7 +20,7 @@
|
|||
<div class="page-content">
|
||||
<div id="id_stats_errors" class="alert alert-error"></div>
|
||||
<div class="center-charts">
|
||||
<h1 class="analytics-page-header">{% trans %}Zulip analytics for {{ realm_name }}{% endtrans %}</h1>
|
||||
<h1 class="analytics-page-header">{% trans %}Zulip analytics for {{ target_realm_name }}{% endtrans %}</h1>
|
||||
|
||||
<div class="left">
|
||||
<div class="chart-container">
|
||||
|
|
|
@ -452,7 +452,8 @@ def require_server_admin(view_func: ViewFuncT) -> ViewFuncT:
|
|||
def require_server_admin_api(view_func: ViewFuncT) -> ViewFuncT:
|
||||
@zulip_login_required
|
||||
@wraps(view_func)
|
||||
def _wrapped_view_func(request: HttpRequest, user_profile: UserProfile, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
def _wrapped_view_func(request: HttpRequest, user_profile: UserProfile, *args: Any,
|
||||
**kwargs: Any) -> HttpResponse:
|
||||
if not user_profile.is_staff:
|
||||
raise JsonableError(_("Must be an server administrator"))
|
||||
return view_func(request, user_profile, *args, **kwargs)
|
||||
|
|
Loading…
Reference in New Issue