support: Show confirmation links in search.

Fixes #13060 #12784
This commit is contained in:
Vishnu KS 2019-09-18 18:34:36 +05:30 committed by Tim Abbott
parent e080b42fe5
commit ec955f8f78
6 changed files with 187 additions and 9 deletions

View File

@ -4,6 +4,7 @@ from typing import List, Optional
import mock
from django.utils.timezone import utc
from django.http import HttpResponse
import ujson
from analytics.lib.counts import COUNT_STATS, CountStat
from analytics.lib.time_utils import time_range
@ -14,7 +15,9 @@ from analytics.views import rewrite_client_arrays, \
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timestamp import ceiling_to_day, \
ceiling_to_hour, datetime_to_timestamp
from zerver.models import Client, get_realm
from zerver.lib.actions import do_create_multiuse_invite_link, \
do_send_realm_reactivation_email
from zerver.models import Client, get_realm, MultiuseInvite
class TestStatsEndpoint(ZulipTestCase):
def test_stats(self) -> None:
@ -335,7 +338,7 @@ class TestSupportEndpoint(ZulipTestCase):
self.assert_in_success_response(['<span class="label">user</span>\n', '<h3>King Hamlet</h3>',
'<b>Email</b>: hamlet@zulip.com', '<b>Is active</b>: True<br>',
'<b>Admins</b>: iago@zulip.com\n',
'class="copy-button" data-admin-emails="iago@zulip.com"'
'class="copy-button" data-copytext="iago@zulip.com"'
], result)
def check_zulip_realm_query_result(result: HttpResponse) -> None:
@ -362,6 +365,39 @@ class TestSupportEndpoint(ZulipTestCase):
'scrub-realm-button">',
'data-string-id="lear"'], result)
def check_preregistration_user_query_result(result: HttpResponse, email: str, invite: Optional[bool]=False) -> None:
self.assert_in_success_response(['<span class="label">preregistration user</span>\n',
'<b>Email</b>: {}'.format(email),
], result)
if invite:
self.assert_in_success_response(['<span class="label">invite</span>'], result)
self.assert_in_success_response(['<b>Expires in</b>: 1\xa0week, 3',
'<b>Status</b>: Link has never been clicked'], result)
self.assert_in_success_response([], result)
else:
self.assert_not_in_success_response(['<span class="label">invite</span>'], result)
self.assert_in_success_response(['<b>Expires in</b>: 1\xa0day',
'<b>Status</b>: Link has never been clicked'], result)
def check_realm_creation_query_result(result: HttpResponse, email: str) -> None:
self.assert_in_success_response(['<span class="label">preregistration user</span>\n',
'<span class="label">realm creation</span>\n',
'<b>Link</b>: http://zulip.testserver/accounts/do_confirm/',
'<b>Expires in</b>: 1\xa0day<br>\n'
], result)
def check_multiuse_invite_link_query_result(result: HttpResponse) -> None:
self.assert_in_success_response(['<span class="label">multiuse invite</span>\n',
'<b>Link</b>: http://zulip.testserver/join/',
'<b>Expires in</b>: 1\xa0week, 3'
], result)
def check_realm_reactivation_link_query_result(result: HttpResponse) -> None:
self.assert_in_success_response(['<span class="label">realm reactivation</span>\n',
'<b>Link</b>: http://zulip.testserver/reactivate/',
'<b>Expires in</b>: 1\xa0day'
], result)
cordelia_email = self.example_email("cordelia")
self.login(cordelia_email)
@ -399,6 +435,36 @@ class TestSupportEndpoint(ZulipTestCase):
check_zulip_realm_query_result(result)
check_lear_realm_query_result(result)
self.client_post('/accounts/home/', {'email': self.nonreg_email("test")})
self.login(iago_email)
result = self.client_get("/activity/support", {"q": self.nonreg_email("test")})
check_preregistration_user_query_result(result, self.nonreg_email("test"))
check_zulip_realm_query_result(result)
stream_ids = [self.get_stream_id("Denmark")]
invitee_emails = [self.nonreg_email("test1")]
self.client_post("/json/invites", {"invitee_emails": invitee_emails,
"stream_ids": ujson.dumps(stream_ids), "invite_as": 1})
result = self.client_get("/activity/support", {"q": self.nonreg_email("test1")})
check_preregistration_user_query_result(result, self.nonreg_email("test1"), invite=True)
check_zulip_realm_query_result(result)
email = self.nonreg_email('alice')
self.client_post('/new/', {'email': email})
result = self.client_get("/activity/support", {"q": email})
check_realm_creation_query_result(result, email)
do_create_multiuse_invite_link(self.example_user("hamlet"), invited_as=1)
result = self.client_get("/activity/support", {"q": "zulip"})
check_multiuse_invite_link_query_result(result)
check_zulip_realm_query_result(result)
MultiuseInvite.objects.all().delete()
do_send_realm_reactivation_email(get_realm("zulip"))
result = self.client_get("/activity/support", {"q": "zulip"})
check_realm_reactivation_link_query_result(result)
check_zulip_realm_query_result(result)
def test_change_plan_type(self) -> None:
cordelia = self.example_user("cordelia")
self.login(cordelia.email)

View File

@ -20,6 +20,7 @@ from django.shortcuts import render
from django.template import loader
from django.utils.timezone import now as timezone_now, utc as timezone_utc
from django.utils.translation import ugettext as _
from django.utils.timesince import timesince
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from jinja2 import Markup as mark_safe
@ -28,6 +29,7 @@ from analytics.lib.counts import COUNT_STATS, CountStat
from analytics.lib.time_utils import time_range
from analytics.models import BaseCount, InstallationCount, \
RealmCount, StreamCount, UserCount, last_successful_fill, installation_epoch
from confirmation.models import Confirmation, confirmation_url, _properties
from zerver.decorator import require_server_admin, require_server_admin_api, \
to_non_negative_int, to_utc_datetime, zulip_login_required, require_non_guest_user
from zerver.lib.exceptions import JsonableError
@ -39,12 +41,13 @@ from zerver.views.invite import get_invitee_emails_set
from zerver.lib.subdomains import get_subdomain_from_hostname
from zerver.lib.actions import do_change_plan_type, do_deactivate_realm, \
do_reactivate_realm, do_scrub_realm
from confirmation.settings import STATUS_ACTIVE
if settings.BILLING_ENABLED:
from corporate.lib.stripe import attach_discount_to_realm, get_discount_for_realm
from zerver.models import Client, get_realm, Realm, \
UserActivity, UserActivityInterval, UserProfile
from zerver.models import Client, get_realm, Realm, UserActivity, UserActivityInterval, \
UserProfile, PreregistrationUser, MultiuseInvite
if settings.ZILENCER_ENABLED:
from zilencer.models import RemoteInstallationCount, RemoteRealmCount, \
@ -1027,6 +1030,46 @@ def get_activity(request: HttpRequest) -> HttpResponse:
context=dict(data=data, title=title, is_home=True),
)
def get_confirmations(types: List[int], object_ids: List[int],
hostname: Optional[str]=None) -> List[Dict[str, Any]]:
lowest_datetime = timezone_now() - timedelta(days=30)
confirmations = Confirmation.objects.filter(type__in=types, object_id__in=object_ids,
date_sent__gte=lowest_datetime)
confirmation_dicts = []
for confirmation in confirmations:
realm = confirmation.realm
content_object = confirmation.content_object
if realm is not None:
realm_host = realm.host
elif isinstance(content_object, Realm):
realm_host = content_object.host
else:
realm_host = hostname
type = confirmation.type
days_to_activate = _properties[type].validity_in_days
expiry_date = confirmation.date_sent + timedelta(days=days_to_activate)
if hasattr(content_object, "status"):
if content_object.status == STATUS_ACTIVE:
link_status = "Link has been clicked"
else:
link_status = "Link has never been clicked"
else:
link_status = ""
if timezone_now() < expiry_date:
expires_in = timesince(confirmation.date_sent, expiry_date)
else:
expires_in = "Expired"
url = confirmation_url(confirmation.confirmation_key, realm_host, type)
confirmation_dicts.append({"object": confirmation.content_object,
"url": url, "type": type, "link_status": link_status,
"expires_in": expires_in})
return confirmation_dicts
@require_server_admin
def support(request: HttpRequest) -> HttpResponse:
context = {} # type: Dict[str, Any]
@ -1091,12 +1134,27 @@ def support(request: HttpRequest) -> HttpResponse:
context["realms"] = realms
confirmations = [] # type: List[Dict[str, Any]]
preregistration_users = PreregistrationUser.objects.filter(email__in=key_words)
confirmations += get_confirmations([Confirmation.USER_REGISTRATION, Confirmation.INVITATION,
Confirmation.REALM_CREATION], preregistration_users,
hostname=request.get_host())
multiuse_invites = MultiuseInvite.objects.filter(realm__in=realms)
confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE], multiuse_invites)
confirmations += get_confirmations([Confirmation.REALM_REACTIVATION], [realm.id for realm in realms])
context["confirmations"] = confirmations
def realm_admin_emails(realm: Realm) -> str:
return ", ".join(realm.get_human_admin_users().values_list("delivery_email", flat=True))
context["realm_admin_emails"] = realm_admin_emails
context["get_discount_for_realm"] = get_discount_for_realm
context["realm_icon_url"] = realm_icon_url
context["Confirmation"] = Confirmation
return render(request, 'analytics/support.html', context=context)
def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:

View File

@ -9,6 +9,6 @@ $(function () {
});
$('a.copy-button').click(function () {
common.copy_data_attribute_value($(this), "admin-emails");
common.copy_data_attribute_value($(this), "copytext");
});
});

View File

@ -5,7 +5,7 @@
<a target="_blank" href="/realm_activity/{{ realm.string_id }}/">activity</a><br>
<b>Date created</b>: {{ realm.date_created|timesince }} ago<br>
<b>Admins</b>: {{ realm_admin_emails(realm) }}
<a title="Copy emails" class="copy-button" data-admin-emails="{{ realm_admin_emails(realm) }}">
<a title="Copy emails" class="copy-button" data-copytext="{{ realm_admin_emails(realm) }}">
<i class="fa fa-copy"></i>
</a>
<form method="POST">
@ -43,4 +43,3 @@
<input type="hidden" name="scrub_realm" value="scrub_realm" />
<button data-string-id="{{realm.string_id}}" class="button rounded btn-danger small scrub-realm-button">Scrub realm (danger)</button>
</form>
<hr>

View File

@ -41,7 +41,7 @@
<b>Date joined</b>: {{ user.date_joined|timesince }} ago<br>
<b>Is active</b>: {{ user.is_active }}<br>
<b>Is admin</b>: {{ user.is_realm_admin }}<br>
<br>
<hr>
<div>
{% include "analytics/realm_details.html" %}
</div>
@ -55,6 +55,61 @@
</div>
{% endfor %}
{% for confirmation in confirmations %}
{% set object = confirmation.object %}
<div class="support-query-result new-style">
{% if confirmation.type == Confirmation.USER_REGISTRATION %}
<span class="label">preregistration user</span>
{% set email = object.email %}
{% set realm = object.realm %}
{% set show_realm_details = True %}
{% elif confirmation.type == Confirmation.REALM_CREATION %}
<span class="label">preregistration user</span>
<span class="label">realm creation</span>
{% set email = object.email %}
{% set show_realm_details = False %}
{% elif confirmation.type == Confirmation.INVITATION %}
<span class="label">preregistration user</span>
<span class="label">invite</span>
{% set email = object.email %}
{% set realm = object.realm %}
{% set show_realm_details = True %}
{% elif confirmation.type == Confirmation.MULTIUSE_INVITE %}
<span class="label">multiuse invite</span>
{% set realm = object.realm %}
{% set show_realm_details = False %}
{% elif confirmation.type == Confirmation.REALM_REACTIVATION %}
<span class="label">realm reactivation</span>
{% set realm = object %}
{% set show_realm_details = False %}
{% endif %}
<br>
<br>
{% if email %}
<b>Email</b>: {{ email }}<br>
{% endif %}
<b>Link</b>: {{ confirmation.url }}
<a title="Copy link" class="copy-button" data-copytext="{{ confirmation.url }}">
<i class="fa fa-copy"></i>
</a><br>
<b>Expires in</b>: {{ confirmation.expires_in }}<br>
{% if confirmation.link_status %}
<b>Status</b>: {{ confirmation.link_status }}
{% endif %}
<br>
{% if show_realm_details %}
<hr>
<div>
{% include "analytics/realm_details.html" %}
</div>
{% elif realm %}
<b>Realm</b>: {{ realm.string_id }}
<br>
{% endif %}
<br>
</div>
<br>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -631,7 +631,7 @@ html_rules = whitespace_rules + prose_style_rules + [
('templates/zerver/app/markdown_help.html',
'<td class="rendered_markdown"><img alt=":heart:" class="emoji" src="/static/generated/emoji/images/emoji/heart.png" title=":heart:" /></td>')
]),
'exclude': set(["templates/zerver/emails", "templates/analytics/realm_details.html"]),
'exclude': set(["templates/zerver/emails", "templates/analytics/realm_details.html", "templates/analytics/support.html"]),
'description': "`title` value should be translatable."},
{'pattern': r'''\Walt=["'][^{"']''',
'description': "alt argument should be enclosed by _() or it should be an empty string.",