mirror of https://github.com/zulip/zulip.git
installation-activity: Add export version of chart.
Adds a boolean export parameter to the installation activity view. When true, the initial links column is removed from the chart, a blank column is removed before human message counts anda column with all organization owner and admin emails is added. Updates the links at the top of the installation activity page to have an option to toggle between the export and non-export versions of the chart.
This commit is contained in:
parent
c2abb5b4a7
commit
55d7ec843e
|
@ -1,4 +1,5 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
@ -8,6 +9,7 @@ from django.template import loader
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from psycopg2.sql import SQL
|
from psycopg2.sql import SQL
|
||||||
|
from pydantic import Json
|
||||||
|
|
||||||
from analytics.lib.counts import COUNT_STATS
|
from analytics.lib.counts import COUNT_STATS
|
||||||
from corporate.lib.activity import (
|
from corporate.lib.activity import (
|
||||||
|
@ -26,10 +28,11 @@ from corporate.lib.activity import (
|
||||||
from corporate.lib.stripe import cents_to_dollar_string
|
from corporate.lib.stripe import cents_to_dollar_string
|
||||||
from corporate.views.support import get_plan_type_string
|
from corporate.views.support import get_plan_type_string
|
||||||
from zerver.decorator import require_server_admin
|
from zerver.decorator import require_server_admin
|
||||||
from zerver.lib.typed_endpoint import typed_endpoint_without_parameters
|
from zerver.lib.typed_endpoint import typed_endpoint
|
||||||
from zerver.models import Realm
|
from zerver.models import Realm
|
||||||
from zerver.models.realm_audit_logs import AuditLogEventType
|
from zerver.models.realm_audit_logs import AuditLogEventType
|
||||||
from zerver.models.realms import get_org_type_display_name
|
from zerver.models.realms import get_org_type_display_name
|
||||||
|
from zerver.models.users import UserProfile
|
||||||
|
|
||||||
|
|
||||||
def get_realm_day_counts() -> dict[str, dict[str, Markup]]:
|
def get_realm_day_counts() -> dict[str, dict[str, Markup]]:
|
||||||
|
@ -88,7 +91,7 @@ def get_realm_day_counts() -> dict[str, dict[str, Markup]]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def realm_summary_table() -> str:
|
def realm_summary_table(export: bool) -> str:
|
||||||
now = timezone_now()
|
now = timezone_now()
|
||||||
|
|
||||||
query = SQL(
|
query = SQL(
|
||||||
|
@ -103,7 +106,8 @@ def realm_summary_table() -> str:
|
||||||
coalesce(user_count_table.value, 0) user_profile_count,
|
coalesce(user_count_table.value, 0) user_profile_count,
|
||||||
coalesce(bot_count_table.value, 0) bot_count,
|
coalesce(bot_count_table.value, 0) bot_count,
|
||||||
coalesce(realm_audit_log_table.how_realm_creator_found_zulip, '') how_realm_creator_found_zulip,
|
coalesce(realm_audit_log_table.how_realm_creator_found_zulip, '') how_realm_creator_found_zulip,
|
||||||
coalesce(realm_audit_log_table.how_realm_creator_found_zulip_extra_context, '') how_realm_creator_found_zulip_extra_context
|
coalesce(realm_audit_log_table.how_realm_creator_found_zulip_extra_context, '') how_realm_creator_found_zulip_extra_context,
|
||||||
|
realm_admin_user.delivery_email admin_email
|
||||||
FROM
|
FROM
|
||||||
zerver_realm as realm
|
zerver_realm as realm
|
||||||
LEFT OUTER JOIN (
|
LEFT OUTER JOIN (
|
||||||
|
@ -168,6 +172,17 @@ def realm_summary_table() -> str:
|
||||||
WHERE
|
WHERE
|
||||||
event_type = %(realm_creation_event_type)s
|
event_type = %(realm_creation_event_type)s
|
||||||
) as realm_audit_log_table ON realm.id = realm_audit_log_table.realm_id
|
) as realm_audit_log_table ON realm.id = realm_audit_log_table.realm_id
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT
|
||||||
|
delivery_email,
|
||||||
|
realm_id
|
||||||
|
from
|
||||||
|
zerver_userprofile
|
||||||
|
WHERE
|
||||||
|
is_bot=False
|
||||||
|
AND is_active=True
|
||||||
|
AND role IN %(admin_roles)s
|
||||||
|
) as realm_admin_user ON realm.id = realm_admin_user.realm_id
|
||||||
WHERE
|
WHERE
|
||||||
_14day_active_humans IS NOT NULL
|
_14day_active_humans IS NOT NULL
|
||||||
or realm.plan_type = 3
|
or realm.plan_type = 3
|
||||||
|
@ -190,11 +205,25 @@ def realm_summary_table() -> str:
|
||||||
"active_users_audit:is_bot:day"
|
"active_users_audit:is_bot:day"
|
||||||
].last_successful_fill(),
|
].last_successful_fill(),
|
||||||
"realm_creation_event_type": AuditLogEventType.REALM_CREATED,
|
"realm_creation_event_type": AuditLogEventType.REALM_CREATED,
|
||||||
|
"admin_roles": (UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
rows = dictfetchall(cursor)
|
raw_rows = dictfetchall(cursor)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
rows: list[dict[str, Any]] = []
|
||||||
|
admin_emails: dict[str, str] = {}
|
||||||
|
# Process duplicate realm rows due to multiple admin users,
|
||||||
|
# and collect all admin user emails into one string.
|
||||||
|
for row in raw_rows:
|
||||||
|
realm_string_id = row["string_id"]
|
||||||
|
admin_email = row.pop("admin_email")
|
||||||
|
if realm_string_id in admin_emails:
|
||||||
|
admin_emails[realm_string_id] = admin_emails[realm_string_id] + ", " + admin_email
|
||||||
|
else:
|
||||||
|
admin_emails[realm_string_id] = admin_email
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
realm_messages_per_day_counts = get_realm_day_counts()
|
realm_messages_per_day_counts = get_realm_day_counts()
|
||||||
total_arr = 0
|
total_arr = 0
|
||||||
num_active_sites = 0
|
num_active_sites = 0
|
||||||
|
@ -253,6 +282,10 @@ def realm_summary_table() -> str:
|
||||||
total_bot_count += int(row["bot_count"])
|
total_bot_count += int(row["bot_count"])
|
||||||
total_wau_count += int(row["wau_count"])
|
total_wau_count += int(row["wau_count"])
|
||||||
|
|
||||||
|
# Add admin users email string
|
||||||
|
if export:
|
||||||
|
row["admin_emails"] = admin_emails[realm_string_id]
|
||||||
|
|
||||||
total_row = [
|
total_row = [
|
||||||
"Total",
|
"Total",
|
||||||
"",
|
"",
|
||||||
|
@ -277,6 +310,9 @@ def realm_summary_table() -> str:
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if export:
|
||||||
|
total_row.pop(1)
|
||||||
|
|
||||||
content = loader.render_to_string(
|
content = loader.render_to_string(
|
||||||
"corporate/activity/installation_activity_table.html",
|
"corporate/activity/installation_activity_table.html",
|
||||||
dict(
|
dict(
|
||||||
|
@ -285,15 +321,16 @@ def realm_summary_table() -> str:
|
||||||
num_active_sites=num_active_sites,
|
num_active_sites=num_active_sites,
|
||||||
utctime=now.strftime("%Y-%m-%d %H:%M %Z"),
|
utctime=now.strftime("%Y-%m-%d %H:%M %Z"),
|
||||||
billing_enabled=settings.BILLING_ENABLED,
|
billing_enabled=settings.BILLING_ENABLED,
|
||||||
|
export=export,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
@require_server_admin
|
@require_server_admin
|
||||||
@typed_endpoint_without_parameters
|
@typed_endpoint
|
||||||
def get_installation_activity(request: HttpRequest) -> HttpResponse:
|
def get_installation_activity(request: HttpRequest, *, export: Json[bool] = False) -> HttpResponse:
|
||||||
content: str = realm_summary_table()
|
content: str = realm_summary_table(export)
|
||||||
title = "Installation activity"
|
title = "Installation activity"
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
|
|
|
@ -2,11 +2,16 @@
|
||||||
|
|
||||||
<p class="installation-activity-header">{{ utctime }}</p>
|
<p class="installation-activity-header">{{ utctime }}</p>
|
||||||
|
|
||||||
<h4>Installation information:</h4>
|
<h4>Other views:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/stats/installation">Server total /stats style graphs</a></li>
|
<li><a href="/stats/installation">Server total /stats style graphs</a></li>
|
||||||
<li><a href="/activity/remote">Remote servers</a></li>
|
<li><a href="/activity/remote">Remote servers</a></li>
|
||||||
<li><a href="/activity/integrations">Integrations by client</a></li>
|
<li><a href="/activity/integrations">Integrations by client</a></li>
|
||||||
|
{% if not export %}
|
||||||
|
<li><a href="/activity?export=true">Export installation chart</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="/activity">Non-export installation chart</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>Counts chart key:</h4>
|
<h4>Counts chart key:</h4>
|
||||||
|
@ -33,7 +38,9 @@
|
||||||
|
|
||||||
<thead class="activity-head">
|
<thead class="activity-head">
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if not export %}
|
||||||
<th>Links</th>
|
<th>Links</th>
|
||||||
|
{% endif %}
|
||||||
<th>Realm</th>
|
<th>Realm</th>
|
||||||
<th>Created (green if ≤12wk)</th>
|
<th>Created (green if ≤12wk)</th>
|
||||||
{% if billing_enabled %}
|
{% if billing_enabled %}
|
||||||
|
@ -47,21 +54,26 @@
|
||||||
<th>WAU</th>
|
<th>WAU</th>
|
||||||
<th>Total users</th>
|
<th>Total users</th>
|
||||||
<th>Bots</th>
|
<th>Bots</th>
|
||||||
|
{% if not export %}
|
||||||
<th></th>
|
<th></th>
|
||||||
|
{% endif %}
|
||||||
<th colspan=8>Human messages sent, last 8 UTC days (today-so-far first)</th>
|
<th colspan=8>Human messages sent, last 8 UTC days (today-so-far first)</th>
|
||||||
|
{% if export %}
|
||||||
|
<th>Admin emails</th>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if not export %}
|
||||||
<td>
|
<td>
|
||||||
{{ row.realm_url }}
|
{{ row.realm_url }}
|
||||||
{{ row.stats_link }}
|
{{ row.stats_link }}
|
||||||
{{ row.support_link }}
|
{{ row.support_link }}
|
||||||
</td>
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
{{ row.activity_link }}
|
{{ row.activity_link }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -113,14 +125,20 @@
|
||||||
<td class="number">
|
<td class="number">
|
||||||
{{ row.bot_count }}
|
{{ row.bot_count }}
|
||||||
</td>
|
</td>
|
||||||
|
{% if not export %}
|
||||||
<td> </td>
|
<td> </td>
|
||||||
|
{% endif %}
|
||||||
{% if row.history %}
|
{% if row.history %}
|
||||||
{{ row.history|safe }}
|
{{ row.history|safe }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<td colspan=8></td>
|
<td colspan=8></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if export %}
|
||||||
|
<td>
|
||||||
|
{{ row.admin_emails }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
Loading…
Reference in New Issue