support: Add basic information about realm.

Adds non-form section to Zulip Cloud support view with some basic
realm information: organization type, plan type, non-guest user
count and guest user count.

Uses a shared template for the basic realm data and adds a shared
support context dict for variables that are used in both remote
and Zulip Cloud support views.
This commit is contained in:
Lauryn Menard 2024-08-26 18:01:20 +02:00 committed by Tim Abbott
parent 349954e4fc
commit 44e73eecc1
7 changed files with 62 additions and 23 deletions

View File

@ -154,23 +154,27 @@ def get_cached_seat_count(realm: Realm) -> int:
return get_latest_seat_count(realm) return get_latest_seat_count(realm)
def get_seat_count( def get_non_guest_user_count(realm: Realm) -> int:
realm: Realm, extra_non_guests_count: int = 0, extra_guests_count: int = 0 return (
) -> int:
non_guests = (
UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False) UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False)
.exclude(role=UserProfile.ROLE_GUEST) .exclude(role=UserProfile.ROLE_GUEST)
.count() .count()
) + extra_non_guests_count
# This guest count calculation should match the similar query in render_stats().
guests = (
UserProfile.objects.filter(
realm=realm, is_active=True, is_bot=False, role=UserProfile.ROLE_GUEST
).count()
+ extra_guests_count
) )
def get_guest_user_count(realm: Realm) -> int:
# Same query to get guest user count as in render_stats in analytics/views/stats.py.
return UserProfile.objects.filter(
realm=realm, is_active=True, is_bot=False, role=UserProfile.ROLE_GUEST
).count()
def get_seat_count(
realm: Realm, extra_non_guests_count: int = 0, extra_guests_count: int = 0
) -> int:
non_guests = get_non_guest_user_count(realm) + extra_non_guests_count
guests = get_guest_user_count(realm) + extra_guests_count
# This formula achieves the pricing of the first 5*N guests # This formula achieves the pricing of the first 5*N guests
# being free of charge (where N is the number of non-guests in the organization) # being free of charge (where N is the number of non-guests in the organization)
# and each consecutive one being worth 1/5 the non-guest price. # and each consecutive one being worth 1/5 the non-guest price.

View File

@ -15,6 +15,8 @@ from corporate.lib.stripe import (
RemoteRealmBillingSession, RemoteRealmBillingSession,
RemoteServerBillingSession, RemoteServerBillingSession,
get_configured_fixed_price_plan_offer, get_configured_fixed_price_plan_offer,
get_guest_user_count,
get_non_guest_user_count,
get_price_per_license, get_price_per_license,
get_push_status_for_remote_request, get_push_status_for_remote_request,
start_of_next_billing_cycle, start_of_next_billing_cycle,
@ -110,10 +112,17 @@ class RemoteSupportData:
mobile_push_data: MobilePushData mobile_push_data: MobilePushData
@dataclass
class UserData:
guest_user_count: int
non_guest_user_count: int
@dataclass @dataclass
class CloudSupportData: class CloudSupportData:
plan_data: PlanData plan_data: PlanData
sponsorship_data: SponsorshipData sponsorship_data: SponsorshipData
user_data: UserData
def get_stripe_customer_url(stripe_id: str) -> str: def get_stripe_customer_url(stripe_id: str) -> str:
@ -129,6 +138,15 @@ def get_realm_support_url(realm: Realm) -> str:
return support_url return support_url
def get_realm_user_data(realm: Realm) -> UserData:
non_guests = get_non_guest_user_count(realm)
guests = get_guest_user_count(realm)
return UserData(
guest_user_count=guests,
non_guest_user_count=non_guests,
)
def get_customer_sponsorship_data(customer: Customer) -> SponsorshipData: def get_customer_sponsorship_data(customer: Customer) -> SponsorshipData:
pending = customer.sponsorship_pending pending = customer.sponsorship_pending
licenses = customer.minimum_licenses licenses = customer.minimum_licenses
@ -421,6 +439,7 @@ def get_data_for_remote_support_view(billing_session: BillingSession) -> RemoteS
def get_data_for_cloud_support_view(billing_session: BillingSession) -> CloudSupportData: def get_data_for_cloud_support_view(billing_session: BillingSession) -> CloudSupportData:
assert isinstance(billing_session, RealmBillingSession) assert isinstance(billing_session, RealmBillingSession)
user_data = get_realm_user_data(billing_session.realm)
plan_data = get_plan_data_for_support_view(billing_session) plan_data = get_plan_data_for_support_view(billing_session)
if plan_data.customer is not None: if plan_data.customer is not None:
sponsorship_data = get_customer_sponsorship_data(plan_data.customer) sponsorship_data = get_customer_sponsorship_data(plan_data.customer)
@ -430,4 +449,5 @@ def get_data_for_cloud_support_view(billing_session: BillingSession) -> CloudSup
return CloudSupportData( return CloudSupportData(
plan_data=plan_data, plan_data=plan_data,
sponsorship_data=sponsorship_data, sponsorship_data=sponsorship_data,
user_data=user_data,
) )

View File

@ -747,7 +747,7 @@ class TestSupportEndpoint(ZulipTestCase):
def test_realm_support_view_queries(self) -> None: def test_realm_support_view_queries(self) -> None:
iago = self.example_user("iago") iago = self.example_user("iago")
self.login_user(iago) self.login_user(iago)
with self.assert_database_query_count(16): with self.assert_database_query_count(18):
result = self.client_get("/activity/support", {"q": "zulip"}, subdomain="zulip") result = self.client_get("/activity/support", {"q": "zulip"}, subdomain="zulip")
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)

View File

@ -344,6 +344,12 @@ VALID_BILLING_MODALITY_VALUES = Literal[
"charge_automatically", "charge_automatically",
] ]
SHARED_SUPPORT_CONTEXT = {
"get_org_type_display_name": get_org_type_display_name,
"get_plan_type_name": get_plan_type_string,
"dollar_amount": cents_to_dollar_string,
}
@require_server_admin @require_server_admin
@typed_endpoint @typed_endpoint
@ -368,7 +374,7 @@ def support(
org_type: Json[NonNegativeInt] | None = None, org_type: Json[NonNegativeInt] | None = None,
max_invites: Json[NonNegativeInt] | None = None, max_invites: Json[NonNegativeInt] | None = None,
) -> HttpResponse: ) -> HttpResponse:
context: dict[str, Any] = {} context: dict[str, Any] = {**SHARED_SUPPORT_CONTEXT}
if "success_message" in request.session: if "success_message" in request.session:
context["success_message"] = request.session["success_message"] context["success_message"] = request.session["success_message"]
@ -602,7 +608,6 @@ def support(
context["get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string context["get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string
context["get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string context["get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string
context["dollar_amount"] = cents_to_dollar_string
context["realm_icon_url"] = realm_icon_url context["realm_icon_url"] = realm_icon_url
context["Confirmation"] = Confirmation context["Confirmation"] = Confirmation
context["REALM_PLAN_TYPES"] = get_realm_plan_type_options() context["REALM_PLAN_TYPES"] = get_realm_plan_type_options()
@ -691,7 +696,7 @@ def remote_servers_support(
] ]
| None = None, | None = None,
) -> HttpResponse: ) -> HttpResponse:
context: dict[str, Any] = {} context: dict[str, Any] = {**SHARED_SUPPORT_CONTEXT}
if "success_message" in request.session: if "success_message" in request.session:
context["success_message"] = request.session["success_message"] context["success_message"] = request.session["success_message"]
@ -876,10 +881,7 @@ def remote_servers_support(
context["remote_server_to_max_monthly_messages"] = remote_server_to_max_monthly_messages context["remote_server_to_max_monthly_messages"] = remote_server_to_max_monthly_messages
context["remote_realms"] = remote_realms context["remote_realms"] = remote_realms
context["remote_realms_support_data"] = realm_support_data context["remote_realms_support_data"] = realm_support_data
context["get_plan_type_name"] = get_plan_type_string
context["get_org_type_display_name"] = get_org_type_display_name
context["format_optional_datetime"] = format_optional_datetime context["format_optional_datetime"] = format_optional_datetime
context["dollar_amount"] = cents_to_dollar_string
context["server_analytics_link"] = remote_installation_stats_link context["server_analytics_link"] = remote_installation_stats_link
context["REMOTE_PLAN_TIERS"] = get_remote_plan_tier_options() context["REMOTE_PLAN_TIERS"] = get_remote_plan_tier_options()
context["get_remote_server_billing_user_emails"] = ( context["get_remote_server_billing_user_emails"] = (

View File

@ -0,0 +1,4 @@
<b>Organization type</b>: {{ get_org_type_display_name(realm.org_type) }}<br />
<b>Plan type</b>: {{ get_plan_type_name(realm.plan_type) }}<br />
<b>Non-guest user count</b>: {{ user_data.non_guest_user_count }}<br />
<b>Guest user count</b>: {{ user_data.guest_user_count }}<br />

View File

@ -33,9 +33,17 @@
<a title="Copy emails" class="copy-button" data-clipboard-text="{{ first_human_user.delivery_email }}"> <a title="Copy emails" class="copy-button" data-clipboard-text="{{ first_human_user.delivery_email }}">
<i class="fa fa-copy"></i> <i class="fa fa-copy"></i>
</a> </a>
<br />
{% else %} {% else %}
<b>First human user</b>: <b>First human user</b>:
<br />
{% endif %} {% endif %}
<br />
{% with %}
{% set realm = realm %}
{% set user_data = realm_support_data[realm.id].user_data %}
{% include 'corporate/support/basic_realm_data.html' %}
{% endwith %}
</div> </div>
<div> <div>
<div class="realm-management-actions"> <div class="realm-management-actions">

View File

@ -29,10 +29,11 @@
<b>Date created</b>: {{ support_data[remote_realm.id].date_created.strftime('%d %B %Y') }}<br /> <b>Date created</b>: {{ support_data[remote_realm.id].date_created.strftime('%d %B %Y') }}<br />
<b>UUID</b>: {{ remote_realm.uuid }}<br /> <b>UUID</b>: {{ remote_realm.uuid }}<br />
<br /> <br />
<b>Organization type</b>: {{ get_org_type_display_name(remote_realm.org_type) }}<br /> {% with %}
<b>Plan type</b>: {{ get_plan_type_name(remote_realm.plan_type) }}<br /> {% set realm = remote_realm %}
<b>Non-guest user count</b>: {{ support_data[remote_realm.id].user_data.non_guest_user_count }}<br /> {% set user_data = support_data[remote_realm.id].user_data %}
<b>Guest user count</b>: {{ support_data[remote_realm.id].user_data.guest_user_count }}<br /> {% include 'corporate/support/basic_realm_data.html' %}
{% endwith %}
<br /> <br />
<b>Mobile user count</b>: {{ support_data[remote_realm.id].mobile_push_data.total_mobile_users }}<br /> <b>Mobile user count</b>: {{ support_data[remote_realm.id].mobile_push_data.total_mobile_users }}<br />
<b>7-day mobile pushes count</b>: {{ support_data[remote_realm.id].mobile_push_data.mobile_pushes_forwarded }}<br /> <b>7-day mobile pushes count</b>: {{ support_data[remote_realm.id].mobile_push_data.mobile_pushes_forwarded }}<br />