support: Add basic support endpoint for remote servers.

This commit is contained in:
Mateusz Mandera 2023-10-04 13:38:12 +02:00 committed by Tim Abbott
parent 2ed1465b04
commit f71e2c8247
5 changed files with 129 additions and 1 deletions

View File

@ -25,6 +25,54 @@ from zerver.models import (
if TYPE_CHECKING: if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
import uuid
from zilencer.models import RemoteZulipServer
class TestRemoteServerSupportEndpoint(ZulipTestCase):
def setUp(self) -> None:
super().setUp()
# Set up some initial example data.
for i in range(20):
hostname = f"zulip-{i}.example.com"
RemoteZulipServer.objects.create(
hostname=hostname, contact_email=f"admin@{hostname}", plan_type=1, uuid=uuid.uuid4()
)
def test_search(self) -> None:
self.login("cordelia")
result = self.client_get("/activity/remote/support")
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
# Iago is the user with the appropriate permissions to access this page.
self.login("iago")
assert self.example_user("iago").is_staff
result = self.client_get("/activity/remote/support")
self.assert_in_success_response(
[
'input type="text" name="q" class="input-xxlarge search-query" placeholder="hostname or contact email"'
],
result,
)
result = self.client_get("/activity/remote/support", {"q": "zulip-1.example.com"})
self.assert_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
self.assert_not_in_success_response(["<h3>zulip-2.example.com</h3>"], result)
result = self.client_get("/activity/remote/support", {"q": "example.com"})
for i in range(20):
self.assert_in_success_response([f"<h3>zulip-{i}.example.com</h3>"], result)
result = self.client_get("/activity/remote/support", {"q": "admin@zulip-2.example.com"})
self.assert_in_success_response(["<h3>zulip-2.example.com</h3>"], result)
self.assert_in_success_response(["<b>Contact email</b>: admin@zulip-2.example.com"], result)
self.assert_not_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
class TestSupportEndpoint(ZulipTestCase): class TestSupportEndpoint(ZulipTestCase):
def test_search(self) -> None: def test_search(self) -> None:

View File

@ -21,7 +21,7 @@ from analytics.views.stats import (
stats_for_remote_installation, stats_for_remote_installation,
stats_for_remote_realm, stats_for_remote_realm,
) )
from analytics.views.support import support from analytics.views.support import remote_servers_support, support
from analytics.views.user_activity import get_user_activity from analytics.views.user_activity import get_user_activity
from zerver.lib.rest import rest_path from zerver.lib.rest import rest_path
@ -30,6 +30,7 @@ i18n_urlpatterns: List[Union[URLPattern, URLResolver]] = [
path("activity", get_installation_activity), path("activity", get_installation_activity),
path("activity/remote", get_remote_server_activity), path("activity/remote", get_remote_server_activity),
path("activity/support", support, name="support"), path("activity/support", support, name="support"),
path("activity/remote/support", remote_servers_support, name="remote_servers_support"),
path("realm_activity/<realm_str>/", get_realm_activity), path("realm_activity/<realm_str>/", get_realm_activity),
path("user_activity/<user_profile_id>/", get_user_activity), path("user_activity/<user_profile_id>/", get_user_activity),
path("stats/realm/<realm_str>/", stats_for_realm), path("stats/realm/<realm_str>/", stats_for_realm),

View File

@ -17,6 +17,7 @@ from django.utils.timesince import timesince
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from analytics.views.activity_common import remote_installation_stats_link
from confirmation.models import Confirmation, confirmation_url from confirmation.models import Confirmation, confirmation_url
from confirmation.settings import STATUS_USED from confirmation.settings import STATUS_USED
from zerver.actions.create_realm import do_change_realm_subdomain from zerver.actions.create_realm import do_change_realm_subdomain
@ -48,6 +49,9 @@ from zerver.models import (
) )
from zerver.views.invite import get_invitee_emails_set from zerver.views.invite import get_invitee_emails_set
if settings.ZILENCER_ENABLED:
from zilencer.models import RemoteZulipServer
if settings.BILLING_ENABLED: if settings.BILLING_ENABLED:
from corporate.lib.stripe import approve_sponsorship as do_approve_sponsorship from corporate.lib.stripe import approve_sponsorship as do_approve_sponsorship
from corporate.lib.stripe import ( from corporate.lib.stripe import (
@ -406,3 +410,44 @@ def support(
) )
return render(request, "analytics/support.html", context=context) return render(request, "analytics/support.html", context=context)
def get_remote_servers_for_support(
email_to_search: Optional[str], hostname_to_search: Optional[str]
) -> List["RemoteZulipServer"]:
if not email_to_search and not hostname_to_search:
return []
remote_servers_query = RemoteZulipServer.objects.order_by("id")
if email_to_search:
remote_servers_query = remote_servers_query.filter(contact_email__iexact=email_to_search)
elif hostname_to_search:
remote_servers_query = remote_servers_query.filter(hostname__icontains=hostname_to_search)
return list(remote_servers_query)
@require_server_admin
@has_request_variables
def remote_servers_support(
request: HttpRequest, query: Optional[str] = REQ("q", default=None)
) -> HttpResponse:
email_to_search = None
hostname_to_search = None
if query:
if "@" in query:
email_to_search = query
else:
hostname_to_search = query
remote_servers = get_remote_servers_for_support(
email_to_search=email_to_search, hostname_to_search=hostname_to_search
)
return render(
request,
"analytics/remote_server_support.html",
context=dict(
remote_servers=remote_servers,
remote_installation_stats_link=remote_installation_stats_link,
),
)

View File

@ -0,0 +1,33 @@
{% extends "zerver/base.html" %}
{% set entrypoint = "support" %}
{# Remote servers. #}
{% block title %}
<title>Remote servers</title>
{% endblock %}
{% block content %}
<div class="container">
<br />
<form>
<center>
<input type="text" name="q" class="input-xxlarge search-query" placeholder="hostname or contact email" value="{{ request.GET.get('q', '') }}" autofocus />
<button type="submit" class="btn btn-default support-search-button">Search</button>
</center>
</form>
<div id="remote-server-query-results">
{% for remote_server in remote_servers %}
<div class="support-query-result">
<span class="label">remote server</span>
<h3>{{ remote_server.hostname }}</h3>
<b>Contact email</b>: {{ remote_server.contact_email }}<br />
<b>Last updated</b>: {{ remote_server.last_updated|timesince }} ago<br />
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -548,6 +548,7 @@ html_rules: List["Rule"] = [
}, },
"exclude": { "exclude": {
"templates/analytics/support.html", "templates/analytics/support.html",
"templates/analytics/remote_server_support.html",
# We have URL template and Pygments language name as placeholders # We have URL template and Pygments language name as placeholders
# in the below template which we don't want to be translatable. # in the below template which we don't want to be translatable.
"web/templates/settings/playground_settings_admin.hbs", "web/templates/settings/playground_settings_admin.hbs",