From 44e73eecc1d71d5f8d06fb8ad18b6eb0b69c9642 Mon Sep 17 00:00:00 2001 From: Lauryn Menard Date: Mon, 26 Aug 2024 18:01:20 +0200 Subject: [PATCH] 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. --- corporate/lib/stripe.py | 28 +++++++++++-------- corporate/lib/support.py | 20 +++++++++++++ corporate/tests/test_support_views.py | 2 +- corporate/views/support.py | 14 ++++++---- .../corporate/support/basic_realm_data.html | 4 +++ .../corporate/support/realm_details.html | 8 ++++++ .../support/remote_realm_details.html | 9 +++--- 7 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 templates/corporate/support/basic_realm_data.html diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index d9bc15e3ea..8a00b90e54 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -154,23 +154,27 @@ def get_cached_seat_count(realm: Realm) -> int: return get_latest_seat_count(realm) -def get_seat_count( - realm: Realm, extra_non_guests_count: int = 0, extra_guests_count: int = 0 -) -> int: - non_guests = ( +def get_non_guest_user_count(realm: Realm) -> int: + return ( UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False) .exclude(role=UserProfile.ROLE_GUEST) .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 # 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. diff --git a/corporate/lib/support.py b/corporate/lib/support.py index cb008d992e..192734c1c1 100644 --- a/corporate/lib/support.py +++ b/corporate/lib/support.py @@ -15,6 +15,8 @@ from corporate.lib.stripe import ( RemoteRealmBillingSession, RemoteServerBillingSession, get_configured_fixed_price_plan_offer, + get_guest_user_count, + get_non_guest_user_count, get_price_per_license, get_push_status_for_remote_request, start_of_next_billing_cycle, @@ -110,10 +112,17 @@ class RemoteSupportData: mobile_push_data: MobilePushData +@dataclass +class UserData: + guest_user_count: int + non_guest_user_count: int + + @dataclass class CloudSupportData: plan_data: PlanData sponsorship_data: SponsorshipData + user_data: UserData def get_stripe_customer_url(stripe_id: str) -> str: @@ -129,6 +138,15 @@ def get_realm_support_url(realm: Realm) -> str: 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: pending = customer.sponsorship_pending 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: assert isinstance(billing_session, RealmBillingSession) + user_data = get_realm_user_data(billing_session.realm) plan_data = get_plan_data_for_support_view(billing_session) if plan_data.customer is not None: 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( plan_data=plan_data, sponsorship_data=sponsorship_data, + user_data=user_data, ) diff --git a/corporate/tests/test_support_views.py b/corporate/tests/test_support_views.py index 373a20a3c0..1ddad1e3a2 100644 --- a/corporate/tests/test_support_views.py +++ b/corporate/tests/test_support_views.py @@ -747,7 +747,7 @@ class TestSupportEndpoint(ZulipTestCase): def test_realm_support_view_queries(self) -> None: iago = self.example_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") self.assertEqual(result.status_code, 200) diff --git a/corporate/views/support.py b/corporate/views/support.py index 60b05f04b7..cb13c229b9 100644 --- a/corporate/views/support.py +++ b/corporate/views/support.py @@ -344,6 +344,12 @@ VALID_BILLING_MODALITY_VALUES = Literal[ "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 @typed_endpoint @@ -368,7 +374,7 @@ def support( org_type: Json[NonNegativeInt] | None = None, max_invites: Json[NonNegativeInt] | None = None, ) -> HttpResponse: - context: dict[str, Any] = {} + context: dict[str, Any] = {**SHARED_SUPPORT_CONTEXT} if "success_message" in request.session: 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_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["Confirmation"] = Confirmation context["REALM_PLAN_TYPES"] = get_realm_plan_type_options() @@ -691,7 +696,7 @@ def remote_servers_support( ] | None = None, ) -> HttpResponse: - context: dict[str, Any] = {} + context: dict[str, Any] = {**SHARED_SUPPORT_CONTEXT} if "success_message" in request.session: 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_realms"] = remote_realms 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["dollar_amount"] = cents_to_dollar_string context["server_analytics_link"] = remote_installation_stats_link context["REMOTE_PLAN_TIERS"] = get_remote_plan_tier_options() context["get_remote_server_billing_user_emails"] = ( diff --git a/templates/corporate/support/basic_realm_data.html b/templates/corporate/support/basic_realm_data.html new file mode 100644 index 0000000000..07d65be5b2 --- /dev/null +++ b/templates/corporate/support/basic_realm_data.html @@ -0,0 +1,4 @@ +Organization type: {{ get_org_type_display_name(realm.org_type) }}
+Plan type: {{ get_plan_type_name(realm.plan_type) }}
+Non-guest user count: {{ user_data.non_guest_user_count }}
+Guest user count: {{ user_data.guest_user_count }}
diff --git a/templates/corporate/support/realm_details.html b/templates/corporate/support/realm_details.html index 5f4a848ab4..7604e092b6 100644 --- a/templates/corporate/support/realm_details.html +++ b/templates/corporate/support/realm_details.html @@ -33,9 +33,17 @@ +
{% else %} First human user: +
{% endif %} +
+ {% with %} + {% set realm = realm %} + {% set user_data = realm_support_data[realm.id].user_data %} + {% include 'corporate/support/basic_realm_data.html' %} + {% endwith %}
diff --git a/templates/corporate/support/remote_realm_details.html b/templates/corporate/support/remote_realm_details.html index 7fe0fec953..0fdbfe1897 100644 --- a/templates/corporate/support/remote_realm_details.html +++ b/templates/corporate/support/remote_realm_details.html @@ -29,10 +29,11 @@ Date created: {{ support_data[remote_realm.id].date_created.strftime('%d %B %Y') }}
UUID: {{ remote_realm.uuid }}

- Organization type: {{ get_org_type_display_name(remote_realm.org_type) }}
- Plan type: {{ get_plan_type_name(remote_realm.plan_type) }}
- Non-guest user count: {{ support_data[remote_realm.id].user_data.non_guest_user_count }}
- Guest user count: {{ support_data[remote_realm.id].user_data.guest_user_count }}
+ {% with %} + {% set realm = remote_realm %} + {% set user_data = support_data[remote_realm.id].user_data %} + {% include 'corporate/support/basic_realm_data.html' %} + {% endwith %}
Mobile user count: {{ support_data[remote_realm.id].mobile_push_data.total_mobile_users }}
7-day mobile pushes count: {{ support_data[remote_realm.id].mobile_push_data.mobile_pushes_forwarded }}