stats: Add 1 day actives and total users to number of users chart.

This commit is contained in:
Nikhil Kumar Mishra 2018-05-19 13:43:02 -07:00 committed by Tim Abbott
parent 5ddc6c21e9
commit fa9d79e203
6 changed files with 94 additions and 19 deletions

View File

@ -85,17 +85,33 @@ class Command(BaseCommand):
None: self.generate_fixture_data(stat, .08, .02, 3, .3, 6, partial_sum=True),
} # type: Mapping[Optional[str], List[int]]
insert_fixture_data(stat, realm_data, RealmCount)
installation_data = {
None: self.generate_fixture_data(stat, .8, .2, 4, .3, 6, partial_sum=True),
} # type: Mapping[Optional[str], List[int]]
insert_fixture_data(stat, installation_data, InstallationCount)
FillState.objects.create(property=stat.property, end_time=last_end_time,
state=FillState.DONE)
stat = COUNT_STATS['realm_active_humans::day']
realm_data = {
None: self.generate_fixture_data(stat, .1, .03, 3, .5, 3, partial_sum=True),
} # type: Mapping[Optional[str], List[int]]
}
insert_fixture_data(stat, realm_data, RealmCount)
installation_data = {
None: self.generate_fixture_data(stat, 1, .3, 4, .5, 3, partial_sum=True),
} # type: Mapping[Optional[str], List[int]]
}
insert_fixture_data(stat, installation_data, InstallationCount)
FillState.objects.create(property=stat.property, end_time=last_end_time,
state=FillState.DONE)
stat = COUNT_STATS['active_users_audit:is_bot:day']
realm_data = {
'false': self.generate_fixture_data(stat, .1, .03, 3.5, .8, 2, partial_sum=True),
}
insert_fixture_data(stat, realm_data, RealmCount)
installation_data = {
'false': self.generate_fixture_data(stat, 1, .3, 6, .8, 2, partial_sum=True),
}
insert_fixture_data(stat, installation_data, InstallationCount)
FillState.objects.create(property=stat.property, end_time=last_end_time,
state=FillState.DONE)

View File

@ -92,6 +92,10 @@ class TestGetChartData(ZulipTestCase):
def test_number_of_humans(self) -> None:
stat = COUNT_STATS['realm_active_humans::day']
self.insert_data(stat, [None], [])
stat = COUNT_STATS['1day_actives::day']
self.insert_data(stat, [None], [])
stat = COUNT_STATS['active_users_audit:is_bot:day']
self.insert_data(stat, ['false'], [])
result = self.client_get('/json/analytics/chart_data',
{'chart_name': 'number_of_humans'})
self.assert_json_success(result)
@ -100,7 +104,7 @@ class TestGetChartData(ZulipTestCase):
'msg': '',
'end_times': [datetime_to_timestamp(dt) for dt in self.end_times_day],
'frequency': CountStat.DAY,
'everyone': {'human': self.data(100)},
'everyone': {'_1day': self.data(100), '_15day': self.data(100), 'all_time': self.data(100)},
'display_order': None,
'result': 'success',
})
@ -173,7 +177,7 @@ class TestGetChartData(ZulipTestCase):
{'chart_name': 'number_of_humans'})
self.assert_json_success(result)
data = result.json()
self.assertEqual(data['everyone'], {'human': [0]})
self.assertEqual(data['everyone'], {"_1day": [0], "_15day": [0], "all_time": [0]})
self.assertFalse('user' in data)
FillState.objects.create(
@ -213,6 +217,10 @@ class TestGetChartData(ZulipTestCase):
def test_start_and_end(self) -> None:
stat = COUNT_STATS['realm_active_humans::day']
self.insert_data(stat, [None], [])
stat = COUNT_STATS['1day_actives::day']
self.insert_data(stat, [None], [])
stat = COUNT_STATS['active_users_audit:is_bot:day']
self.insert_data(stat, ['false'], [])
end_time_timestamps = [datetime_to_timestamp(dt) for dt in self.end_times_day]
# valid start and end
@ -223,7 +231,7 @@ class TestGetChartData(ZulipTestCase):
self.assert_json_success(result)
data = result.json()
self.assertEqual(data['end_times'], end_time_timestamps[1:3])
self.assertEqual(data['everyone'], {'human': [0, 100]})
self.assertEqual(data['everyone'], {'_1day': [0, 100], '_15day': [0, 100], 'all_time': [0, 100]})
# start later then end
result = self.client_get('/json/analytics/chart_data',
@ -235,6 +243,10 @@ class TestGetChartData(ZulipTestCase):
def test_min_length(self) -> None:
stat = COUNT_STATS['realm_active_humans::day']
self.insert_data(stat, [None], [])
stat = COUNT_STATS['1day_actives::day']
self.insert_data(stat, [None], [])
stat = COUNT_STATS['active_users_audit:is_bot:day']
self.insert_data(stat, ['false'], [])
# test min_length is too short to change anything
result = self.client_get('/json/analytics/chart_data',
{'chart_name': 'number_of_humans',
@ -242,7 +254,7 @@ class TestGetChartData(ZulipTestCase):
self.assert_json_success(result)
data = result.json()
self.assertEqual(data['end_times'], [datetime_to_timestamp(dt) for dt in self.end_times_day])
self.assertEqual(data['everyone'], {'human': self.data(100)})
self.assertEqual(data['everyone'], {'_1day': self.data(100), '_15day': self.data(100), 'all_time': self.data(100)})
# test min_length larger than filled data
result = self.client_get('/json/analytics/chart_data',
{'chart_name': 'number_of_humans',
@ -251,7 +263,7 @@ class TestGetChartData(ZulipTestCase):
data = result.json()
end_times = [ceiling_to_day(self.realm.date_created) + timedelta(days=i) for i in range(-1, 4)]
self.assertEqual(data['end_times'], [datetime_to_timestamp(dt) for dt in end_times])
self.assertEqual(data['everyone'], {'human': [0]+self.data(100)})
self.assertEqual(data['everyone'], {'_1day': [0]+self.data(100), '_15day': [0]+self.data(100), 'all_time': [0]+self.data(100)})
def test_non_existent_chart(self) -> None:
result = self.client_get('/json/analytics/chart_data',

View File

@ -94,9 +94,15 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
aggregate_table = InstallationCount
if chart_name == 'number_of_humans':
stats = [COUNT_STATS['realm_active_humans::day']]
stats = [
COUNT_STATS['1day_actives::day'],
COUNT_STATS['realm_active_humans::day'],
COUNT_STATS['active_users_audit:is_bot:day']]
tables = [aggregate_table]
subgroup_to_label = {stats[0]: {None: 'human'}} # type: Dict[CountStat, Dict[Optional[str], str]]
subgroup_to_label = {
stats[0]: {None: '_1day'},
stats[1]: {None: '_15day'},
stats[2]: {'false': 'all_time'}} # type: Dict[CountStat, Dict[Optional[str], str]]
labels_sort_function = None
include_empty_subgroups = True
elif chart_name == 'messages_sent_over_time':

View File

@ -649,19 +649,49 @@ function populate_number_of_users(data) {
var text = end_dates.map(format_date);
var trace = {
function make_traces(values, type) {
return {
x: end_dates,
y: data.everyone.human,
type: 'scatter',
name: "Active users",
y: values,
type: type,
name: i18n.t("Active users"),
hoverinfo: 'none',
text: text,
visible: true,
};
}
var _1day_trace = make_traces(data.everyone._1day, 'bar');
var _15day_trace = make_traces(data.everyone._15day, 'scatter');
var all_time_trace = make_traces(data.everyone.all_time, 'scatter');
$('#id_number_of_users > div').removeClass("spinner");
// Redraw the plot every time for simplicity. If we have perf problems with this in the
// future, we can copy the update behavior from populate_messages_sent_over_time
function draw_or_update_plot(trace) {
$('#1day_actives_button, #15day_actives_button, #all_time_actives_button').removeClass("selected");
Plotly.newPlot('id_number_of_users', [trace], layout, {displayModeBar: false});
}
$('#1day_actives_button').click(function () {
draw_or_update_plot(_1day_trace);
$(this).addClass("selected");
});
$('#15day_actives_button').click(function () {
draw_or_update_plot(_15day_trace);
$(this).addClass("selected");
});
$('#all_time_actives_button').click(function () {
draw_or_update_plot(all_time_trace);
$(this).addClass("selected");
});
// Initial drawing of plot
draw_or_update_plot(_15day_trace, true);
$('#15day_actives_button').addClass("selected");
document.getElementById('id_number_of_users').on('plotly_hover', function (data) {
$("#users_hover_info").show();

View File

@ -104,6 +104,10 @@ hr {
margin: 3px;
}
.button-active-users {
float: right;
}
.chart-container .button-container > * {
display: inline-block;
vertical-align: top;

View File

@ -85,6 +85,13 @@
<div class="chart-container">
<h1>{{ _("Active users") }}</h1>
<div class="button-container">
<div class="buttons button-active-users">
<button class="button" type="button" id="1day_actives_button">{{ _("Daily actives") }}</button>
<button class="button" type="button" id="15day_actives_button">{{ _("15 day actives") }}</button>
<button class="button" type="button" id="all_time_actives_button">{{ _("Total users") }}</button>
</div>
</div>
<div id="id_number_of_users">
<div class="spinner"></div>
</div>