mirror of https://github.com/zulip/zulip.git
stats: Add 1 day actives and total users to number of users chart.
This commit is contained in:
parent
5ddc6c21e9
commit
fa9d79e203
|
@ -85,17 +85,33 @@ class Command(BaseCommand):
|
||||||
None: self.generate_fixture_data(stat, .08, .02, 3, .3, 6, partial_sum=True),
|
None: self.generate_fixture_data(stat, .08, .02, 3, .3, 6, partial_sum=True),
|
||||||
} # type: Mapping[Optional[str], List[int]]
|
} # type: Mapping[Optional[str], List[int]]
|
||||||
insert_fixture_data(stat, realm_data, RealmCount)
|
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,
|
FillState.objects.create(property=stat.property, end_time=last_end_time,
|
||||||
state=FillState.DONE)
|
state=FillState.DONE)
|
||||||
|
|
||||||
stat = COUNT_STATS['realm_active_humans::day']
|
stat = COUNT_STATS['realm_active_humans::day']
|
||||||
realm_data = {
|
realm_data = {
|
||||||
None: self.generate_fixture_data(stat, .1, .03, 3, .5, 3, partial_sum=True),
|
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)
|
insert_fixture_data(stat, realm_data, RealmCount)
|
||||||
installation_data = {
|
installation_data = {
|
||||||
None: self.generate_fixture_data(stat, 1, .3, 4, .5, 3, partial_sum=True),
|
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)
|
insert_fixture_data(stat, installation_data, InstallationCount)
|
||||||
FillState.objects.create(property=stat.property, end_time=last_end_time,
|
FillState.objects.create(property=stat.property, end_time=last_end_time,
|
||||||
state=FillState.DONE)
|
state=FillState.DONE)
|
||||||
|
|
|
@ -92,6 +92,10 @@ class TestGetChartData(ZulipTestCase):
|
||||||
def test_number_of_humans(self) -> None:
|
def test_number_of_humans(self) -> None:
|
||||||
stat = COUNT_STATS['realm_active_humans::day']
|
stat = COUNT_STATS['realm_active_humans::day']
|
||||||
self.insert_data(stat, [None], [])
|
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',
|
result = self.client_get('/json/analytics/chart_data',
|
||||||
{'chart_name': 'number_of_humans'})
|
{'chart_name': 'number_of_humans'})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
@ -100,7 +104,7 @@ class TestGetChartData(ZulipTestCase):
|
||||||
'msg': '',
|
'msg': '',
|
||||||
'end_times': [datetime_to_timestamp(dt) for dt in self.end_times_day],
|
'end_times': [datetime_to_timestamp(dt) for dt in self.end_times_day],
|
||||||
'frequency': CountStat.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,
|
'display_order': None,
|
||||||
'result': 'success',
|
'result': 'success',
|
||||||
})
|
})
|
||||||
|
@ -173,7 +177,7 @@ class TestGetChartData(ZulipTestCase):
|
||||||
{'chart_name': 'number_of_humans'})
|
{'chart_name': 'number_of_humans'})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
data = result.json()
|
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)
|
self.assertFalse('user' in data)
|
||||||
|
|
||||||
FillState.objects.create(
|
FillState.objects.create(
|
||||||
|
@ -213,6 +217,10 @@ class TestGetChartData(ZulipTestCase):
|
||||||
def test_start_and_end(self) -> None:
|
def test_start_and_end(self) -> None:
|
||||||
stat = COUNT_STATS['realm_active_humans::day']
|
stat = COUNT_STATS['realm_active_humans::day']
|
||||||
self.insert_data(stat, [None], [])
|
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]
|
end_time_timestamps = [datetime_to_timestamp(dt) for dt in self.end_times_day]
|
||||||
|
|
||||||
# valid start and end
|
# valid start and end
|
||||||
|
@ -223,7 +231,7 @@ class TestGetChartData(ZulipTestCase):
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
data = result.json()
|
data = result.json()
|
||||||
self.assertEqual(data['end_times'], end_time_timestamps[1:3])
|
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
|
# start later then end
|
||||||
result = self.client_get('/json/analytics/chart_data',
|
result = self.client_get('/json/analytics/chart_data',
|
||||||
|
@ -235,6 +243,10 @@ class TestGetChartData(ZulipTestCase):
|
||||||
def test_min_length(self) -> None:
|
def test_min_length(self) -> None:
|
||||||
stat = COUNT_STATS['realm_active_humans::day']
|
stat = COUNT_STATS['realm_active_humans::day']
|
||||||
self.insert_data(stat, [None], [])
|
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
|
# test min_length is too short to change anything
|
||||||
result = self.client_get('/json/analytics/chart_data',
|
result = self.client_get('/json/analytics/chart_data',
|
||||||
{'chart_name': 'number_of_humans',
|
{'chart_name': 'number_of_humans',
|
||||||
|
@ -242,7 +254,7 @@ class TestGetChartData(ZulipTestCase):
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
data = result.json()
|
data = result.json()
|
||||||
self.assertEqual(data['end_times'], [datetime_to_timestamp(dt) for dt in self.end_times_day])
|
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
|
# test min_length larger than filled data
|
||||||
result = self.client_get('/json/analytics/chart_data',
|
result = self.client_get('/json/analytics/chart_data',
|
||||||
{'chart_name': 'number_of_humans',
|
{'chart_name': 'number_of_humans',
|
||||||
|
@ -251,7 +263,7 @@ class TestGetChartData(ZulipTestCase):
|
||||||
data = result.json()
|
data = result.json()
|
||||||
end_times = [ceiling_to_day(self.realm.date_created) + timedelta(days=i) for i in range(-1, 4)]
|
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['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:
|
def test_non_existent_chart(self) -> None:
|
||||||
result = self.client_get('/json/analytics/chart_data',
|
result = self.client_get('/json/analytics/chart_data',
|
||||||
|
|
|
@ -94,9 +94,15 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
|
||||||
aggregate_table = InstallationCount
|
aggregate_table = InstallationCount
|
||||||
|
|
||||||
if chart_name == 'number_of_humans':
|
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]
|
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
|
labels_sort_function = None
|
||||||
include_empty_subgroups = True
|
include_empty_subgroups = True
|
||||||
elif chart_name == 'messages_sent_over_time':
|
elif chart_name == 'messages_sent_over_time':
|
||||||
|
|
|
@ -649,19 +649,49 @@ function populate_number_of_users(data) {
|
||||||
|
|
||||||
var text = end_dates.map(format_date);
|
var text = end_dates.map(format_date);
|
||||||
|
|
||||||
var trace = {
|
function make_traces(values, type) {
|
||||||
|
return {
|
||||||
x: end_dates,
|
x: end_dates,
|
||||||
y: data.everyone.human,
|
y: values,
|
||||||
type: 'scatter',
|
type: type,
|
||||||
name: "Active users",
|
name: i18n.t("Active users"),
|
||||||
hoverinfo: 'none',
|
hoverinfo: 'none',
|
||||||
text: text,
|
text: text,
|
||||||
visible: true,
|
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");
|
$('#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});
|
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) {
|
document.getElementById('id_number_of_users').on('plotly_hover', function (data) {
|
||||||
$("#users_hover_info").show();
|
$("#users_hover_info").show();
|
||||||
|
|
|
@ -104,6 +104,10 @@ hr {
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-active-users {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.chart-container .button-container > * {
|
.chart-container .button-container > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
|
@ -85,6 +85,13 @@
|
||||||
|
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<h1>{{ _("Active users") }}</h1>
|
<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 id="id_number_of_users">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue