mirror of https://github.com/zulip/zulip.git
parent
e080b42fe5
commit
ec955f8f78
|
@ -4,6 +4,7 @@ from typing import List, Optional
|
||||||
import mock
|
import mock
|
||||||
from django.utils.timezone import utc
|
from django.utils.timezone import utc
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
import ujson
|
||||||
|
|
||||||
from analytics.lib.counts import COUNT_STATS, CountStat
|
from analytics.lib.counts import COUNT_STATS, CountStat
|
||||||
from analytics.lib.time_utils import time_range
|
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.test_classes import ZulipTestCase
|
||||||
from zerver.lib.timestamp import ceiling_to_day, \
|
from zerver.lib.timestamp import ceiling_to_day, \
|
||||||
ceiling_to_hour, datetime_to_timestamp
|
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):
|
class TestStatsEndpoint(ZulipTestCase):
|
||||||
def test_stats(self) -> None:
|
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>',
|
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>Email</b>: hamlet@zulip.com', '<b>Is active</b>: True<br>',
|
||||||
'<b>Admins</b>: iago@zulip.com\n',
|
'<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)
|
], result)
|
||||||
|
|
||||||
def check_zulip_realm_query_result(result: HttpResponse) -> None:
|
def check_zulip_realm_query_result(result: HttpResponse) -> None:
|
||||||
|
@ -362,6 +365,39 @@ class TestSupportEndpoint(ZulipTestCase):
|
||||||
'scrub-realm-button">',
|
'scrub-realm-button">',
|
||||||
'data-string-id="lear"'], result)
|
'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")
|
cordelia_email = self.example_email("cordelia")
|
||||||
self.login(cordelia_email)
|
self.login(cordelia_email)
|
||||||
|
|
||||||
|
@ -399,6 +435,36 @@ class TestSupportEndpoint(ZulipTestCase):
|
||||||
check_zulip_realm_query_result(result)
|
check_zulip_realm_query_result(result)
|
||||||
check_lear_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:
|
def test_change_plan_type(self) -> None:
|
||||||
cordelia = self.example_user("cordelia")
|
cordelia = self.example_user("cordelia")
|
||||||
self.login(cordelia.email)
|
self.login(cordelia.email)
|
||||||
|
|
|
@ -20,6 +20,7 @@ from django.shortcuts import render
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.utils.timezone import now as timezone_now, utc as timezone_utc
|
from django.utils.timezone import now as timezone_now, utc as timezone_utc
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils.timesince import timesince
|
||||||
from django.core.validators import URLValidator
|
from django.core.validators import URLValidator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from jinja2 import Markup as mark_safe
|
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.lib.time_utils import time_range
|
||||||
from analytics.models import BaseCount, InstallationCount, \
|
from analytics.models import BaseCount, InstallationCount, \
|
||||||
RealmCount, StreamCount, UserCount, last_successful_fill, installation_epoch
|
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, \
|
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
|
to_non_negative_int, to_utc_datetime, zulip_login_required, require_non_guest_user
|
||||||
from zerver.lib.exceptions import JsonableError
|
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.subdomains import get_subdomain_from_hostname
|
||||||
from zerver.lib.actions import do_change_plan_type, do_deactivate_realm, \
|
from zerver.lib.actions import do_change_plan_type, do_deactivate_realm, \
|
||||||
do_reactivate_realm, do_scrub_realm
|
do_reactivate_realm, do_scrub_realm
|
||||||
|
from confirmation.settings import STATUS_ACTIVE
|
||||||
|
|
||||||
if settings.BILLING_ENABLED:
|
if settings.BILLING_ENABLED:
|
||||||
from corporate.lib.stripe import attach_discount_to_realm, get_discount_for_realm
|
from corporate.lib.stripe import attach_discount_to_realm, get_discount_for_realm
|
||||||
|
|
||||||
from zerver.models import Client, get_realm, Realm, \
|
from zerver.models import Client, get_realm, Realm, UserActivity, UserActivityInterval, \
|
||||||
UserActivity, UserActivityInterval, UserProfile
|
UserProfile, PreregistrationUser, MultiuseInvite
|
||||||
|
|
||||||
if settings.ZILENCER_ENABLED:
|
if settings.ZILENCER_ENABLED:
|
||||||
from zilencer.models import RemoteInstallationCount, RemoteRealmCount, \
|
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),
|
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
|
@require_server_admin
|
||||||
def support(request: HttpRequest) -> HttpResponse:
|
def support(request: HttpRequest) -> HttpResponse:
|
||||||
context = {} # type: Dict[str, Any]
|
context = {} # type: Dict[str, Any]
|
||||||
|
@ -1091,12 +1134,27 @@ def support(request: HttpRequest) -> HttpResponse:
|
||||||
|
|
||||||
context["realms"] = realms
|
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:
|
def realm_admin_emails(realm: Realm) -> str:
|
||||||
return ", ".join(realm.get_human_admin_users().values_list("delivery_email", flat=True))
|
return ", ".join(realm.get_human_admin_users().values_list("delivery_email", flat=True))
|
||||||
|
|
||||||
context["realm_admin_emails"] = realm_admin_emails
|
context["realm_admin_emails"] = realm_admin_emails
|
||||||
context["get_discount_for_realm"] = get_discount_for_realm
|
context["get_discount_for_realm"] = get_discount_for_realm
|
||||||
context["realm_icon_url"] = realm_icon_url
|
context["realm_icon_url"] = realm_icon_url
|
||||||
|
context["Confirmation"] = Confirmation
|
||||||
return render(request, 'analytics/support.html', context=context)
|
return render(request, 'analytics/support.html', context=context)
|
||||||
|
|
||||||
def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:
|
def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:
|
||||||
|
|
|
@ -9,6 +9,6 @@ $(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('a.copy-button').click(function () {
|
$('a.copy-button').click(function () {
|
||||||
common.copy_data_attribute_value($(this), "admin-emails");
|
common.copy_data_attribute_value($(this), "copytext");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<a target="_blank" href="/realm_activity/{{ realm.string_id }}/">activity</a><br>
|
<a target="_blank" href="/realm_activity/{{ realm.string_id }}/">activity</a><br>
|
||||||
<b>Date created</b>: {{ realm.date_created|timesince }} ago<br>
|
<b>Date created</b>: {{ realm.date_created|timesince }} ago<br>
|
||||||
<b>Admins</b>: {{ realm_admin_emails(realm) }}
|
<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>
|
<i class="fa fa-copy"></i>
|
||||||
</a>
|
</a>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
@ -43,4 +43,3 @@
|
||||||
<input type="hidden" name="scrub_realm" value="scrub_realm" />
|
<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>
|
<button data-string-id="{{realm.string_id}}" class="button rounded btn-danger small scrub-realm-button">Scrub realm (danger)</button>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<b>Date joined</b>: {{ user.date_joined|timesince }} ago<br>
|
<b>Date joined</b>: {{ user.date_joined|timesince }} ago<br>
|
||||||
<b>Is active</b>: {{ user.is_active }}<br>
|
<b>Is active</b>: {{ user.is_active }}<br>
|
||||||
<b>Is admin</b>: {{ user.is_realm_admin }}<br>
|
<b>Is admin</b>: {{ user.is_realm_admin }}<br>
|
||||||
<br>
|
<hr>
|
||||||
<div>
|
<div>
|
||||||
{% include "analytics/realm_details.html" %}
|
{% include "analytics/realm_details.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,6 +55,61 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -631,7 +631,7 @@ html_rules = whitespace_rules + prose_style_rules + [
|
||||||
('templates/zerver/app/markdown_help.html',
|
('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>')
|
'<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."},
|
'description': "`title` value should be translatable."},
|
||||||
{'pattern': r'''\Walt=["'][^{"']''',
|
{'pattern': r'''\Walt=["'][^{"']''',
|
||||||
'description': "alt argument should be enclosed by _() or it should be an empty string.",
|
'description': "alt argument should be enclosed by _() or it should be an empty string.",
|
||||||
|
|
Loading…
Reference in New Issue