activity: Add totals row as sticky footer to activity charts.

Updates the total row for the installation and remote activity
charts to be in the table footer. Makes the footer class sticky
to the bottom of the view so that it is always visible on the
chart.

Also, updates the installation activity column for revenue to
be formatted as a dollar string, since this formatting was being
applied in the updated total row.
This commit is contained in:
Lauryn Menard 2024-01-25 15:42:50 +01:00 committed by Tim Abbott
parent 5786a38cec
commit bfd9eec4b3
8 changed files with 50 additions and 24 deletions

View File

@ -31,6 +31,8 @@ def make_table(
title: str, title: str,
cols: Sequence[str], cols: Sequence[str],
rows: Sequence[Any], rows: Sequence[Any],
*,
totals: Optional[Any] = None,
stats_link: Optional[Markup] = None, stats_link: Optional[Markup] = None,
has_row_class: bool = False, has_row_class: bool = False,
) -> str: ) -> str:
@ -41,7 +43,7 @@ def make_table(
rows = list(map(fix_row, rows)) rows = list(map(fix_row, rows))
data = dict(title=title, cols=cols, rows=rows, stats_link=stats_link) data = dict(title=title, cols=cols, rows=rows, totals=totals, stats_link=stats_link)
content = loader.render_to_string( content = loader.render_to_string(
"analytics/ad_hoc_query.html", "analytics/ad_hoc_query.html",

View File

@ -23,6 +23,7 @@ from analytics.views.activity_common import (
realm_url_link, realm_url_link,
) )
from analytics.views.support import get_plan_type_string from analytics.views.support import get_plan_type_string
from corporate.lib.stripe import cents_to_dollar_string
from zerver.decorator import require_server_admin from zerver.decorator import require_server_admin
from zerver.lib.request import has_request_variables from zerver.lib.request import has_request_variables
from zerver.models import Realm from zerver.models import Realm
@ -210,7 +211,7 @@ def realm_summary_table() -> str:
string_id = row["string_id"] string_id = row["string_id"]
if string_id in estimated_arrs: if string_id in estimated_arrs:
row["arr"] = estimated_arrs[string_id] row["arr"] = f"${cents_to_dollar_string(estimated_arrs[string_id])}"
if row["plan_type"] in [Realm.PLAN_TYPE_STANDARD, Realm.PLAN_TYPE_PLUS]: if row["plan_type"] in [Realm.PLAN_TYPE_STANDARD, Realm.PLAN_TYPE_PLUS]:
row["effective_rate"] = 100 - int(realms_with_default_discount.get(string_id, 0)) row["effective_rate"] = 100 - int(realms_with_default_discount.get(string_id, 0))
@ -250,28 +251,34 @@ 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"])
total_row = dict( total_row = [
string_id="Total", "Total",
plan_type_string="", "",
org_type_string="", "",
effective_rate="", "",
arr=total_arr, f"${cents_to_dollar_string(total_arr)}",
realm_url="", "",
stats_link="", "",
support_link="", total_dau_count,
date_created_day="", total_wau_count,
dau_count=total_dau_count, total_user_profile_count,
user_profile_count=total_user_profile_count, total_bot_count,
bot_count=total_bot_count, "",
wau_count=total_wau_count, "",
) "",
"",
rows.insert(0, total_row) "",
"",
"",
"",
"",
]
content = loader.render_to_string( content = loader.render_to_string(
"analytics/realm_summary_table.html", "analytics/realm_summary_table.html",
dict( dict(
rows=rows, rows=rows,
totals=total_row,
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,

View File

@ -145,7 +145,7 @@ def realm_user_summary_table(
return row["cells"][4] return row["cells"][4]
rows = sorted(rows, key=by_last_heard_from, reverse=True) rows = sorted(rows, key=by_last_heard_from, reverse=True)
content = make_table(title, cols, rows, stats_link, has_row_class=True) content = make_table(title, cols, rows, stats_link=stats_link, has_row_class=True)
return content return content

View File

@ -214,9 +214,8 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
total_row.append(str(sum(row[i] for row in rows if row[i] is not None))) total_row.append(str(sum(row[i] for row in rows if row[i] is not None)))
else: else:
total_row.append("") total_row.append("")
rows.insert(0, total_row)
content = make_table(title, cols, rows) content = make_table(title, cols, rows, totals=total_row)
return render( return render(
request, request,
"analytics/activity_details_template.html", "analytics/activity_details_template.html",

View File

@ -70,7 +70,7 @@ def estimate_annual_recurring_revenue_by_realm() -> Dict[str, int]: # nocoverag
).get_customer_plan_renewal_amount(plan, latest_ledger_entry) ).get_customer_plan_renewal_amount(plan, latest_ledger_entry)
if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY: if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
renewal_cents *= 12 renewal_cents *= 12
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100) annual_revenue[plan.customer.realm.string_id] = renewal_cents
return annual_revenue return annual_revenue

View File

@ -23,4 +23,11 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
{% if data.totals %}
<tfoot class="activity-foot">
{% for total in data.totals %}
<td>{{ total }}</td>
{% endfor %}
</tfoot>
{% endif %}
</table> </table>

View File

@ -121,5 +121,9 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
<tfoot class="activity-foot">
{% for total in totals %}
<td>{{ total }}</td>
{% endfor %}
</tfoot>
</table> </table>

View File

@ -12,6 +12,13 @@
} }
} }
.activity-foot {
background-color: hsl(208deg 100% 97%);
font-weight: 700;
position: sticky;
bottom: 0;
}
.table-striped { .table-striped {
& tr.recently_active { & tr.recently_active {
& td { & td {