remote-support: Show deactivated servers in search results.

The remote support view now returns results for deactivated remote
servers with those results sorted to the end and formatted to
visually stand out.

Forms to change sponsorship and discount fields on the customer
for the remote server or realm are not shown, but the data stored
on the customer object is shown, including any sponsorship request
information (if the customer had a sponsorship request pending when
it was deactivated).

Forms to schedule a plan are also not shown for deactivated servers
and their associated remote realms.

Forms and information for any current plan or scheduled plan, for
either the deactivated remote server or its associated remote
realms, are shown so that support staff can update those plans if
necessary.
This commit is contained in:
Lauryn Menard 2024-03-01 17:44:24 +01:00 committed by Tim Abbott
parent a513489f38
commit 666041e480
8 changed files with 118 additions and 37 deletions

View File

@ -190,7 +190,6 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
], ],
html_response, html_response,
) )
self.assert_not_in_success_response(["<h3>zulip-0.example.com"], result)
def assert_realm_details_in_response( def assert_realm_details_in_response(
html_response: "TestHttpResponse", name: str, host: str html_response: "TestHttpResponse", name: str, host: str
@ -207,7 +206,26 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
], ],
html_response, html_response,
) )
self.assert_not_in_success_response(["<h3>zulip-1.example.com"], result) self.assert_not_in_success_response(["<h3>zulip-1.example.com"], html_response)
def check_deactivated_server(result: "TestHttpResponse", hostname: str) -> None:
self.assert_not_in_success_response(
["<b>Sponsorship pending</b>:<br />", "⏱️ Schedule fixed price plan:"], result
)
self.assert_in_success_response(
[
'<span class="label">remote server: deactivated</span>',
f"<h3>{hostname} <a",
f"<b>Contact email</b>: admin@{hostname}",
"<b>Billing users</b>:",
"<b>Date created</b>:",
"<b>UUID</b>:",
"<b>Zulip version</b>:",
"📶 Push notification status:",
"💸 Discount and sponsorship information:",
],
result,
)
def check_remote_server_with_no_realms(result: "TestHttpResponse") -> None: def check_remote_server_with_no_realms(result: "TestHttpResponse") -> None:
assert_server_details_in_response(result, "zulip-1.example.com") assert_server_details_in_response(result, "zulip-1.example.com")
@ -317,12 +335,13 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
result, result,
) )
server = 0
result = self.client_get("/activity/remote/support", {"q": "example.com"}) result = self.client_get("/activity/remote/support", {"q": "example.com"})
self.assert_not_in_success_response([f"<h3>zulip-{server}.example.com"], result)
for i in range(6): for i in range(6):
if i != server: self.assert_in_success_response([f"<h3>zulip-{i}.example.com <a"], result)
self.assert_in_success_response([f"<h3>zulip-{i}.example.com <a"], result)
server = 0
result = self.client_get("/activity/remote/support", {"q": f"zulip-{server}.example.com"})
check_deactivated_server(result, f"zulip-{server}.example.com")
server = 1 server = 1
result = self.client_get("/activity/remote/support", {"q": f"zulip-{server}.example.com"}) result = self.client_get("/activity/remote/support", {"q": f"zulip-{server}.example.com"})
@ -404,10 +423,7 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
server = 0 server = 0
remote_server = RemoteZulipServer.objects.get(hostname=f"zulip-{server}.example.com") remote_server = RemoteZulipServer.objects.get(hostname=f"zulip-{server}.example.com")
result = self.client_get("/activity/remote/support", {"q": f"{remote_server.uuid}"}) result = self.client_get("/activity/remote/support", {"q": f"{remote_server.uuid}"})
self.assert_not_in_success_response( check_deactivated_server(result, f"zulip-{server}.example.com")
['<span class="label">remote server</span>', '<span class="label">remote realm</span>'],
result,
)
unknown_uuid = uuid.uuid4() unknown_uuid = uuid.uuid4()
result = self.client_get("/activity/remote/support", {"q": f"{unknown_uuid}"}) result = self.client_get("/activity/remote/support", {"q": f"{unknown_uuid}"})

View File

@ -3,6 +3,7 @@ from contextlib import suppress
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from operator import attrgetter
from typing import Any, Dict, Iterable, List, Optional, Union from typing import Any, Dict, Iterable, List, Optional, Union
from urllib.parse import urlencode, urlsplit from urllib.parse import urlencode, urlsplit
@ -472,7 +473,7 @@ def support(
def get_remote_servers_for_support( def get_remote_servers_for_support(
email_to_search: Optional[str], uuid_to_search: Optional[str], hostname_to_search: Optional[str] email_to_search: Optional[str], uuid_to_search: Optional[str], hostname_to_search: Optional[str]
) -> List["RemoteZulipServer"]: ) -> List["RemoteZulipServer"]:
remote_servers_query = RemoteZulipServer.objects.order_by("id").exclude(deactivated=True) remote_servers_query = RemoteZulipServer.objects.order_by("id")
if email_to_search: if email_to_search:
remote_servers_set = set(remote_servers_query.filter(contact_email__iexact=email_to_search)) remote_servers_set = set(remote_servers_query.filter(contact_email__iexact=email_to_search))
@ -486,7 +487,7 @@ def get_remote_servers_for_support(
).select_related("remote_realm__server") ).select_related("remote_realm__server")
for realm_billing_user in remote_realm_billing_users: for realm_billing_user in remote_realm_billing_users:
remote_servers_set.add(realm_billing_user.remote_realm.server) remote_servers_set.add(realm_billing_user.remote_realm.server)
return list(remote_servers_set) return sorted(remote_servers_set, key=attrgetter("deactivated"))
if uuid_to_search: if uuid_to_search:
remote_servers_set = set(remote_servers_query.filter(uuid__iexact=uuid_to_search)) remote_servers_set = set(remote_servers_query.filter(uuid__iexact=uuid_to_search))
@ -495,7 +496,7 @@ def get_remote_servers_for_support(
).select_related("server") ).select_related("server")
for remote_realm in remote_realm_matches: for remote_realm in remote_realm_matches:
remote_servers_set.add(remote_realm.server) remote_servers_set.add(remote_realm.server)
return list(remote_servers_set) return sorted(remote_servers_set, key=attrgetter("deactivated"))
if hostname_to_search: if hostname_to_search:
remote_servers_set = set( remote_servers_set = set(
@ -506,7 +507,7 @@ def get_remote_servers_for_support(
).select_related("server") ).select_related("server")
for remote_realm in remote_realm_matches: for remote_realm in remote_realm_matches:
remote_servers_set.add(remote_realm.server) remote_servers_set.add(remote_realm.server)
return list(remote_servers_set) return sorted(remote_servers_set, key=attrgetter("deactivated"))
return [] return []

View File

@ -44,8 +44,15 @@
{% include 'corporate/support/push_status_details.html' %} {% include 'corporate/support/push_status_details.html' %}
{% endwith %} {% endwith %}
{% if remote_server_deactivated %}
{% if remote_realm.plan_type != SPONSORED_PLAN_TYPE %} <div class="remote-support-sponsorship-container">
{% with %}
{% set sponsorship_data = support_data[remote_realm.id].sponsorship_data %}
{% set format_discount = format_discount %}
{% include 'corporate/support/sponsorship_details.html' %}
{% endwith %}
</div>
{% elif remote_realm.plan_type != SPONSORED_PLAN_TYPE %}
<div class="remote-support-sponsorship-container"> <div class="remote-support-sponsorship-container">
{% with %} {% with %}
{% set sponsorship_data = support_data[remote_realm.id].sponsorship_data %} {% set sponsorship_data = support_data[remote_realm.id].sponsorship_data %}
@ -87,7 +94,7 @@
{% include 'corporate/support/next_plan_details.html' %} {% include 'corporate/support/next_plan_details.html' %}
{% endwith %} {% endwith %}
</div> </div>
{% else %} {% elif not remote_server_deactivated %}
<div class="next-plan-container"> <div class="next-plan-container">
{% with %} {% with %}
{% set is_current_plan_billable = support_data[remote_realm.id].plan_data.is_current_plan_billable %} {% set is_current_plan_billable = support_data[remote_realm.id].plan_data.is_current_plan_billable %}

View File

@ -33,10 +33,15 @@
<div id="remote-server-query-results"> <div id="remote-server-query-results">
{% for remote_server in remote_servers %} {% for remote_server in remote_servers %}
<div class="remote-support-query-result"> {% if remote_server.deactivated %}
{% set remote_server_query_result_class = "remote-support-query-result remote-server-deactivated" %}
{% else %}
{% set remote_server_query_result_class = "remote-support-query-result" %}
{% endif %}
<div class="{{ remote_server_query_result_class }}">
<div class="remote-server-section"> <div class="remote-server-section">
<div class="remote-server-information"> <div class="remote-server-information">
<span class="label">remote server</span> <span class="label">remote server{% if remote_server.deactivated %}: deactivated{% endif %}</span>
<h3>{{ remote_server.hostname }} {{ server_analytics_link(remote_server.id ) }}</h3> <h3>{{ remote_server.hostname }} {{ server_analytics_link(remote_server.id ) }}</h3>
{% if remote_server.plan_type == SPONSORED_PLAN_TYPE %} {% if remote_server.plan_type == SPONSORED_PLAN_TYPE %}
<p class="support-section-header">On 100% sponsored Zulip Community plan 🎉</p> <p class="support-section-header">On 100% sponsored Zulip Community plan 🎉</p>
@ -81,7 +86,15 @@
{% include 'corporate/support/push_status_details.html' %} {% include 'corporate/support/push_status_details.html' %}
{% endwith %} {% endwith %}
{% if remote_server.plan_type != SPONSORED_PLAN_TYPE %} {% if remote_server.deactivated %}
<div class="remote-support-sponsorship-container">
{% with %}
{% set sponsorship_data = remote_servers_support_data[remote_server.id].sponsorship_data %}
{% set format_discount = format_discount %}
{% include 'corporate/support/sponsorship_details.html' %}
{% endwith %}
</div>
{% elif remote_server.plan_type != SPONSORED_PLAN_TYPE %}
<div class="remote-support-sponsorship-container"> <div class="remote-support-sponsorship-container">
{% with %} {% with %}
{% set sponsorship_data = remote_servers_support_data[remote_server.id].sponsorship_data %} {% set sponsorship_data = remote_servers_support_data[remote_server.id].sponsorship_data %}
@ -123,7 +136,7 @@
{% include 'corporate/support/next_plan_details.html' %} {% include 'corporate/support/next_plan_details.html' %}
{% endwith %} {% endwith %}
</div> </div>
{% else %} {% elif not remote_server.deactivated %}
<div class="next-plan-container"> <div class="next-plan-container">
{% with %} {% with %}
{% set is_current_plan_billable = remote_servers_support_data[remote_server.id].plan_data.is_current_plan_billable %} {% set is_current_plan_billable = remote_servers_support_data[remote_server.id].plan_data.is_current_plan_billable %}
@ -144,6 +157,7 @@
{% for remote_realm in remote_realms[remote_server.id] %} {% for remote_realm in remote_realms[remote_server.id] %}
<div> <div>
{% with %} {% with %}
{% set remote_server_deactivated = remote_server.deactivated %}
{% set support_data = remote_realms_support_data %} {% set support_data = remote_realms_support_data %}
{% set get_plan_type_name = get_plan_type_name %} {% set get_plan_type_name = get_plan_type_name %}
{% set format_discount = format_discount %} {% set format_discount = format_discount %}

View File

@ -0,0 +1,26 @@
<div class="remote-sponsorship-details">
<p class="support-section-header">💸 Discount and sponsorship information:</p>
<b>Sponsorship pending</b>: {{ sponsorship_data.sponsorship_pending }}<br />
{% if sponsorship_data.default_discount %}
<b>Discount</b>: {{ format_discount(sponsorship_data.default_discount) }}%<br />
{% else %}
<b>Discount</b>: None<br />
{% endif %}
<b>Minimum licenses</b>: {{ sponsorship_data.minimum_licenses }}<br />
{% if sponsorship_data.required_plan_tier %}
{% for plan_tier in REMOTE_PLAN_TIERS %}
{% if sponsorship_data.required_plan_tier == plan_tier.value %}
<b>Required plan tier</b>: {{ plan_tier.name }}<br />
{% endif %}
{% endfor %}
{% else %}
<b>Required plan tier</b>: None<br />
{% endif %}
</div>
{% if sponsorship_data.sponsorship_pending %}
{% with %}
{% set latest_sponsorship_request = sponsorship_data.latest_sponsorship_request %}
{% include 'corporate/support/sponsorship_request_details.html' %}
{% endwith %}
{% endif %}

View File

@ -63,20 +63,8 @@
</form> </form>
{% if sponsorship_data.sponsorship_pending %} {% if sponsorship_data.sponsorship_pending %}
<div class=""> {% with %}
<p class="support-section-header">Sponsorship request information:</p> {% set latest_sponsorship_request = sponsorship_data.latest_sponsorship_request %}
{% if sponsorship_data.latest_sponsorship_request %} {% include 'corporate/support/sponsorship_request_details.html' %}
<ul> {% endwith %}
<li><b>Organization type</b>: {{ sponsorship_data.latest_sponsorship_request.org_type }}</li>
<li><b>Organization website</b>: {{ sponsorship_data.latest_sponsorship_request.org_website }}</li>
<li><b>Organization description</b>: {{ sponsorship_data.latest_sponsorship_request.org_description }}</li>
<li><b>Estimated total users</b>: {{ sponsorship_data.latest_sponsorship_request.total_users }}</li>
<li><b>Paid staff</b>: {{ sponsorship_data.latest_sponsorship_request.paid_users }}</li>
<li><b>Description of paid staff</b>: {{ sponsorship_data.latest_sponsorship_request.paid_users_description }}</li>
<li><b>Requested plan</b>: {{ sponsorship_data.latest_sponsorship_request.requested_plan }}</li>
</ul>
{% else %}
<b>No sponsorship requests have been submitted.</b><br /><br />
{% endif %}
</div>
{% endif %} {% endif %}

View File

@ -0,0 +1,16 @@
<div class="remote-sponsorship-details">
<p class="support-section-header">Sponsorship request information:</p>
{% if latest_sponsorship_request %}
<ul>
<li><b>Organization type</b>: {{ latest_sponsorship_request.org_type }}</li>
<li><b>Organization website</b>: {{ latest_sponsorship_request.org_website }}</li>
<li><b>Organization description</b>: {{ latest_sponsorship_request.org_description }}</li>
<li><b>Estimated total users</b>: {{ latest_sponsorship_request.total_users }}</li>
<li><b>Paid staff</b>: {{ latest_sponsorship_request.paid_users }}</li>
<li><b>Description of paid staff</b>: {{ latest_sponsorship_request.paid_users_description }}</li>
<li><b>Requested plan</b>: {{ latest_sponsorship_request.requested_plan }}</li>
</ul>
{% else %}
<b>No sponsorship requests have been submitted.</b>
{% endif %}
</div>

View File

@ -260,6 +260,18 @@ tr.admin td:first-child {
} }
} }
.remote-support-query-result.remote-server-deactivated {
background-color: hsl(2deg 64% 58%);
.remote-server-section {
background-color: hsl(22deg 100% 92%);
}
.remote-realms-section {
background-color: hsl(23deg 100% 95%);
}
}
.delete-user-form { .delete-user-form {
margin: 8px 0; margin: 8px 0;
} }
@ -288,6 +300,7 @@ tr.admin td:first-child {
top: -2px; top: -2px;
} }
.remote-sponsorship-details,
.support-form, .support-form,
.remote-form, .remote-form,
.next-plan-information, .next-plan-information,