mirror of https://github.com/zulip/zulip.git
support: Add active plan information to remote server activity.
Adds three columns to the remote server activity chart and updates the chart key for the third of those columns. The first is the plan name. If there are multiple plans with a status under the live threshhold, then we send "See support view". The second is the plan status. If there are multiple plans, then we send "Multiple plans". The third is the estimated annual revenue for the plan. Note that for free trials, this will be calculated as if the plan was paid for 12 months (so a full year). If there is no plan for the server under the live threshold or at all then "---" is inserted into the table row. Note that 100% sponsored servers/realms would fall into this category.
This commit is contained in:
parent
484c0df076
commit
a897d68d93
|
@ -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)
|
||||
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<ul>
|
||||
<li><strong>Zulip version</strong> - as of last update time</li>
|
||||
<li><strong>Mobile pushes forwarded</strong> - last 7 days, including today's current count</li>
|
||||
<li><strong>ARR</strong> (annual recurring revenue) - the number of users they are paying for * annual price/user</li>
|
||||
<li><strong>Links</strong>
|
||||
<ul>
|
||||
<li><strong><i class="fa fa-pie-chart"></i></strong> - remote server's stats page</li>
|
||||
|
|
Loading…
Reference in New Issue