diff --git a/analytics/tests/test_activity_views.py b/analytics/tests/test_activity_views.py index d93f82dc07..999afdf0d0 100644 --- a/analytics/tests/test_activity_views.py +++ b/analytics/tests/test_activity_views.py @@ -1,11 +1,18 @@ +import uuid from datetime import timedelta from unittest import mock from django.utils.timezone import now as timezone_now +from corporate.lib.stripe import add_months +from corporate.models import Customer, CustomerPlan, LicenseLedger from zerver.lib.test_classes import ZulipTestCase from zerver.models import Client, UserActivity, UserProfile -from zilencer.models import RemoteRealmAuditLog, get_remote_server_guest_and_non_guest_count +from zilencer.models import ( + RemoteRealmAuditLog, + RemoteZulipServer, + get_remote_server_guest_and_non_guest_count, +) event_time = timezone_now() - timedelta(days=3) data_list = [ @@ -99,8 +106,32 @@ class ActivityTest(ZulipTestCase): result = self.client_get("/activity") self.assertEqual(result.status_code, 200) + # Add data for remote activity page RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list]) - with self.assert_database_query_count(6): + remote_server = RemoteZulipServer.objects.get(id=1) + customer = Customer.objects.create(remote_server=remote_server) + plan = CustomerPlan.objects.create( + customer=customer, + billing_cycle_anchor=timezone_now(), + billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL, + tier=CustomerPlan.TIER_SELF_HOSTED_BUSINESS, + price_per_license=8000, + next_invoice_date=add_months(timezone_now(), 12), + ) + LicenseLedger.objects.create( + licenses=10, + licenses_at_next_renewal=10, + event_time=timezone_now(), + is_renewal=True, + plan=plan, + ) + RemoteZulipServer.objects.create( + uuid=str(uuid.uuid4()), + api_key="magic_secret_api_key", + hostname="demo.example.com", + contact_email="email@example.com", + ) + with self.assert_database_query_count(10): result = self.client_get("/activity/remote") self.assertEqual(result.status_code, 200) diff --git a/analytics/views/remote_activity.py b/analytics/views/remote_activity.py index a066b07065..099e282670 100644 --- a/analytics/views/remote_activity.py +++ b/analytics/views/remote_activity.py @@ -10,6 +10,7 @@ from analytics.views.activity_common import ( remote_installation_stats_link, remote_installation_support_link, ) +from corporate.lib.analytics import get_plan_data_by_remote_server from zerver.decorator import require_server_admin from zilencer.models import get_remote_server_guest_and_non_guest_count @@ -83,6 +84,9 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: "Mobile users", "Last update time", "Mobile pushes forwarded", + "Plan name", + "Plan status", + "ARR", "Non guest users", "Guest users", "Links", @@ -91,13 +95,27 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: rows = get_query_data(query) total_row = [] totals_columns = [4, 5] + plan_data_by_remote_server = get_plan_data_by_remote_server() + for row in rows: - stats = remote_installation_stats_link(row[0]) - support = remote_installation_support_link(row[1]) - links = stats + " " + support + # Add estimated revenue for server + server_plan_data = plan_data_by_remote_server.get(row[0]) + if server_plan_data is None: + row.append("---") + row.append("---") + row.append("---") + else: + row.append(server_plan_data.current_plan_name) + row.append(server_plan_data.current_status) + row.append(server_plan_data.annual_revenue) + # Add user counts remote_server_counts = get_remote_server_guest_and_non_guest_count(row[0]) row.append(remote_server_counts.non_guest_user_count) row.append(remote_server_counts.guest_user_count) + # Add links + stats = remote_installation_stats_link(row[0]) + support = remote_installation_support_link(row[1]) + links = stats + " " + support row.append(links) for i, col in enumerate(cols): if col == "Last update time": diff --git a/corporate/lib/analytics.py b/corporate/lib/analytics.py index da43e0b980..16fa6feecf 100644 --- a/corporate/lib/analytics.py +++ b/corporate/lib/analytics.py @@ -1,13 +1,25 @@ +from dataclasses import dataclass from decimal import Decimal from typing import Any, Dict from django.utils.timezone import now as timezone_now -from corporate.lib.stripe import RealmBillingSession +from corporate.lib.stripe import ( + RealmBillingSession, + RemoteRealmBillingSession, + RemoteServerBillingSession, +) from corporate.models import Customer, CustomerPlan from zerver.lib.utils import assert_is_not_none +@dataclass +class RemoteActivityPlanData: + current_status: str + current_plan_name: str + annual_revenue: int + + def get_realms_with_default_discount_dict() -> Dict[str, Decimal]: realms_with_default_discount: Dict[str, Any] = {} customers = ( @@ -39,3 +51,44 @@ def estimate_annual_recurring_revenue_by_realm() -> Dict[str, int]: # nocoverag # TODO: Decimal stuff annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100) return annual_revenue + + +def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # nocoverage + remote_server_plan_data: Dict[int, RemoteActivityPlanData] = {} + for plan in CustomerPlan.objects.filter( + status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD + ).select_related("customer__remote_server", "customer__remote_realm"): + renewal_cents = 0 + server_id = None + + if plan.customer.remote_server is not None: + server_id = plan.customer.remote_server.id + renewal_cents = RemoteServerBillingSession( + remote_server=plan.customer.remote_server + ).get_customer_plan_renewal_amount(plan, timezone_now()) + elif plan.customer.remote_realm is not None: + server_id = plan.customer.remote_realm.server.id + renewal_cents = RemoteRealmBillingSession( + remote_realm=plan.customer.remote_realm + ).get_customer_plan_renewal_amount(plan, timezone_now()) + + assert server_id is not None + + if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY: + renewal_cents *= 12 + + current_data = remote_server_plan_data.get(server_id) + if current_data is not None: + current_revenue = remote_server_plan_data[server_id].annual_revenue + remote_server_plan_data[server_id] = RemoteActivityPlanData( + current_status="Multiple plans", + current_plan_name="See support view", + annual_revenue=current_revenue + int(renewal_cents / 100), + ) + else: + remote_server_plan_data[server_id] = RemoteActivityPlanData( + current_status=plan.get_plan_status_as_text(), + current_plan_name=plan.name, + annual_revenue=int(renewal_cents / 100), + ) + return remote_server_plan_data diff --git a/templates/analytics/remote_activity_key.html b/templates/analytics/remote_activity_key.html index 816cb3f339..4f3d862ab8 100644 --- a/templates/analytics/remote_activity_key.html +++ b/templates/analytics/remote_activity_key.html @@ -2,6 +2,7 @@