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),
|
||||
} # 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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -649,19 +649,49 @@ function populate_number_of_users(data) {
|
|||
|
||||
var text = end_dates.map(format_date);
|
||||
|
||||
var trace = {
|
||||
x: end_dates,
|
||||
y: data.everyone.human,
|
||||
type: 'scatter',
|
||||
name: "Active users",
|
||||
hoverinfo: 'none',
|
||||
text: text,
|
||||
visible: true,
|
||||
};
|
||||
function make_traces(values, type) {
|
||||
return {
|
||||
x: end_dates,
|
||||
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");
|
||||
|
||||
Plotly.newPlot('id_number_of_users', [trace], layout, {displayModeBar: false});
|
||||
// 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();
|
||||
|
|
|
@ -104,6 +104,10 @@ hr {
|
|||
margin: 3px;
|
||||
}
|
||||
|
||||
.button-active-users {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.chart-container .button-container > * {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue