mirror of https://github.com/zulip/zulip.git
settings: Send email after deactivating user.
This adds a feature where an admin can choose to send an email with custom content to an user after they deactivated them. Fixes #18943.
This commit is contained in:
parent
b8a760b14e
commit
0a278c39d2
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
|
@ -118,6 +118,10 @@ export function launch(conf) {
|
|||
}
|
||||
|
||||
const $submit_button = $dialog.find(".dialog_submit_button");
|
||||
const $send_email_checkbox = $dialog.find(".send_email");
|
||||
const $email_field = $dialog.find(".email_field");
|
||||
|
||||
$email_field.hide();
|
||||
|
||||
// This is used to link the submit button with the form, if present, in the modal.
|
||||
// This makes it so that submitting this form by pressing Enter on an input element
|
||||
|
@ -140,6 +144,14 @@ export function launch(conf) {
|
|||
conf.on_click(e);
|
||||
});
|
||||
|
||||
$($send_email_checkbox).on("change", () => {
|
||||
if ($($send_email_checkbox).is(":checked")) {
|
||||
$email_field.show();
|
||||
} else {
|
||||
$email_field.hide();
|
||||
}
|
||||
});
|
||||
|
||||
overlays.open_modal("dialog_widget_modal", {
|
||||
autoremove: true,
|
||||
on_show: () => {
|
||||
|
|
|
@ -21,6 +21,7 @@ import * as settings_bots from "./settings_bots";
|
|||
import * as settings_config from "./settings_config";
|
||||
import * as settings_data from "./settings_data";
|
||||
import * as settings_panel_menu from "./settings_panel_menu";
|
||||
import * as settings_ui from "./settings_ui";
|
||||
import * as timerender from "./timerender";
|
||||
import * as ui from "./ui";
|
||||
import * as user_pill from "./user_pill";
|
||||
|
@ -442,11 +443,16 @@ export function confirm_deactivation(user_id, handle_confirm, loading_spinner) {
|
|||
|
||||
const bots_owned_by_user = bot_data.get_all_bots_owned_by_user(user_id);
|
||||
const user = people.get_by_user_id(user_id);
|
||||
const realm_uri = page_params.realm_uri;
|
||||
const realm_name = page_params.realm_name;
|
||||
const opts = {
|
||||
username: user.full_name,
|
||||
email: settings_data.email_for_user_settings(user),
|
||||
bots_owned_by_user,
|
||||
number_of_invites_by_user,
|
||||
admin_email: people.my_current_email(),
|
||||
realm_uri,
|
||||
realm_name,
|
||||
};
|
||||
const html_body = render_settings_deactivation_user_modal(opts);
|
||||
|
||||
|
@ -479,6 +485,17 @@ function handle_deactivation($tbody) {
|
|||
function handle_confirm() {
|
||||
const url = "/json/users/" + encodeURIComponent(user_id);
|
||||
dialog_widget.submit_api_request(channel.del, url);
|
||||
|
||||
let data = {};
|
||||
if ($(".send_email").is(":checked")) {
|
||||
data = {
|
||||
deactivation_notification_comment: $(".email_field_textarea").val(),
|
||||
};
|
||||
}
|
||||
|
||||
const $status_field = $("#admin-user-list .alert-notification");
|
||||
|
||||
settings_ui.do_settings_change(channel.del, url, data, $status_field);
|
||||
}
|
||||
|
||||
confirm_deactivation(user_id, handle_confirm, true);
|
||||
|
|
|
@ -124,6 +124,25 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.email_field {
|
||||
margin-top: 10px;
|
||||
|
||||
.email_field_textarea {
|
||||
width: 97%;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid hsla(300, 2%, 11%, 0.3);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mmfadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
|
@ -18,3 +18,30 @@
|
|||
</ul>
|
||||
{{/if}}
|
||||
</p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="send_email" data-key="{{ key }}" />
|
||||
{{#tr}}
|
||||
Notify this user by email?
|
||||
{{/tr}}
|
||||
<span></span>
|
||||
{{> ../help_link_widget link="/help/deactivate-or-reactivate-a-user#notify-users-of-their-deactivation" }}
|
||||
</label>
|
||||
<div class="email_field">
|
||||
<p class="border-top">
|
||||
<strong>{{t "Subject" }}:</strong>
|
||||
{{#tr}}
|
||||
Notification of account deactivation on {realm_name}
|
||||
{{/tr}}
|
||||
</p>
|
||||
<div class="email-body">
|
||||
<p>
|
||||
{{#tr}}
|
||||
Your Zulip account on <z-link></z-link> has been deactivated,
|
||||
and you will no longer be able to log in.
|
||||
{{#*inline "z-link"}}<a href="{{realm_uri}}">{{realm_uri}}</a>{{/inline}}
|
||||
{{/tr}}
|
||||
</p>
|
||||
<p>{{t "The administrators provided the following comment:" }}</p>
|
||||
<textarea class="email_field_textarea" rows="8" maxlength="2000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 6.0
|
||||
|
||||
**Feature level 135**
|
||||
|
||||
* [`DELETE /user/{user_id}`](/api/deactivate-user): Added
|
||||
`deactivation_notification_comment` field controlling whether the
|
||||
user receives a notification email about their deactivation.
|
||||
|
||||
**Feature level 134**
|
||||
|
||||
* [`GET /events`](/api/get-events): Added `user_topic` event type
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "zerver/emails/compiled/email_base_default.html" %}
|
||||
|
||||
{% block illustration %}
|
||||
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% trans %}
|
||||
Your Zulip account on <a href="{{ realm_uri }}">{{ realm_uri }}</a> has been deactivated, and you will no longer be able to log in.
|
||||
{% endtrans %}
|
||||
|
||||
<br/><br/>
|
||||
|
||||
{% if deactivation_notification_comment %}
|
||||
{{ _("The administrators provided the following comment:") }}
|
||||
|
||||
<pre class="deactivated-user-text">{{ deactivation_notification_comment }}</pre>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
{% trans %}Notification of account deactivation on {{ realm_name }}{% endtrans %}
|
|
@ -0,0 +1,9 @@
|
|||
{% trans %}
|
||||
Your Zulip account on {{ realm_uri }} has been deactivated, and you will no longer be able to log in.
|
||||
{% endtrans %}
|
||||
|
||||
{% if deactivation_notification_comment %}
|
||||
{{ _("The administrators provided the following comment:") }}
|
||||
|
||||
{{ deactivation_notification_comment }}
|
||||
{% endif %}
|
|
@ -21,6 +21,11 @@ body {
|
|||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0;
|
||||
|
@ -323,6 +328,13 @@ a.button:hover {
|
|||
color: #15c;
|
||||
}
|
||||
|
||||
.deactivated-user-text {
|
||||
padding: 0 0 0 15px;
|
||||
margin: 0 0 20px;
|
||||
border-left: 5px solid #eee;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class="body"] h1 {
|
||||
font-size: 28px !important;
|
||||
|
|
|
@ -43,6 +43,29 @@ user's bots will also be deactivated. Lastly, the user will be unable to
|
|||
create a new Zulip account in your organization using their deactivated
|
||||
email address.
|
||||
|
||||
### Notify users of their deactivation
|
||||
|
||||
Zulip can optionally send the user an email notification that their account was deactivated.
|
||||
|
||||
{start_tabs}
|
||||
|
||||
{settings_tab|user-list-admin}
|
||||
|
||||
2. Click the **Deactivate** button to the right of the user account that you
|
||||
want to deactivate.
|
||||
|
||||
3. Check the checkbox labeled **"Notify this user by email?"**.
|
||||
|
||||
4. Optional: Enter a custom message for the user in the provided textbox.
|
||||
|
||||
3. Approve by clicking **Confirm**.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
Here is a sample notification email:
|
||||
|
||||
<img src="/static/images/help/deactivate-user-email.png" alt="view-of-admin" width="800"/>
|
||||
|
||||
## Reactivate a user
|
||||
|
||||
Organization administrators can reactivate a deactivated user. The reactivated
|
||||
|
|
|
@ -230,7 +230,7 @@ python_rules = RuleList(
|
|||
rules=[
|
||||
{
|
||||
"pattern": "subject|SUBJECT",
|
||||
"exclude_pattern": "subject to the|email|outbox",
|
||||
"exclude_pattern": "subject to the|email|outbox|account deactivation",
|
||||
"description": "avoid subject as a var",
|
||||
"good_lines": ["topic_name"],
|
||||
"bad_lines": ['subject="foo"', " MAX_SUBJECT_LEN"],
|
||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
|||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in templates/zerver/api/changelog.md, as well as
|
||||
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 134
|
||||
API_FEATURE_LEVEL = 135
|
||||
|
||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||
# only when going from an old version of the code to a newer version. Bump
|
||||
|
|
|
@ -8801,6 +8801,21 @@ paths:
|
|||
given their user ID.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/UserId"
|
||||
- name: deactivation_notification_comment
|
||||
in: query
|
||||
description: |
|
||||
If not `null`, requests that the deactivated user receive
|
||||
a notification email about their account deactivation.
|
||||
|
||||
If not `""`, encodes custom text written by the administrator
|
||||
to be included in the notification email.
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 135).
|
||||
schema:
|
||||
type: string
|
||||
example: |
|
||||
Farewell!
|
||||
required: false
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/SimpleSuccess"
|
||||
|
|
|
@ -1399,6 +1399,42 @@ class ActivateTest(ZulipTestCase):
|
|||
user = self.example_user("hamlet")
|
||||
self.assertTrue(user.is_active)
|
||||
|
||||
def test_email_sent(self) -> None:
|
||||
self.login("iago")
|
||||
user = self.example_user("hamlet")
|
||||
|
||||
# Verify no email sent by default.
|
||||
result = self.client_delete(f"/json/users/{user.id}", dict())
|
||||
self.assert_json_success(result)
|
||||
from django.core.mail import outbox
|
||||
|
||||
self.assert_length(outbox, 0)
|
||||
user.refresh_from_db()
|
||||
self.assertFalse(user.is_active)
|
||||
|
||||
# Reactivate user
|
||||
do_reactivate_user(user, acting_user=None)
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.is_active)
|
||||
|
||||
# Verify no email sent by default.
|
||||
result = self.client_delete(
|
||||
f"/json/users/{user.id}",
|
||||
dict(
|
||||
deactivation_notification_comment="Dear Hamlet,\nyou just got deactivated.",
|
||||
),
|
||||
)
|
||||
self.assert_json_success(result)
|
||||
user.refresh_from_db()
|
||||
self.assertFalse(user.is_active)
|
||||
|
||||
self.assert_length(outbox, 1)
|
||||
msg = outbox[0]
|
||||
self.assertEqual(msg.subject, "Notification of account deactivation on Zulip Dev")
|
||||
self.assert_length(msg.reply_to, 1)
|
||||
self.assertEqual(msg.reply_to[0], "noreply@testserver")
|
||||
self.assertIn("Dear Hamlet,", msg.body)
|
||||
|
||||
def test_api_with_nonexistent_user(self) -> None:
|
||||
self.login("iago")
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ from zerver.lib.integrations import EMBEDDED_BOTS
|
|||
from zerver.lib.rate_limiter import rate_limit_spectator_attachment_access_by_file
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.send_email import FromAddress, send_email
|
||||
from zerver.lib.streams import access_stream_by_id, access_stream_by_name, subscribed_to_stream
|
||||
from zerver.lib.types import ProfileDataElementUpdateDict, ProfileDataElementValue, Validator
|
||||
from zerver.lib.upload import upload_avatar_image
|
||||
|
@ -71,6 +72,7 @@ from zerver.lib.users import (
|
|||
from zerver.lib.utils import generate_api_key
|
||||
from zerver.lib.validator import (
|
||||
check_bool,
|
||||
check_capped_string,
|
||||
check_dict,
|
||||
check_dict_only,
|
||||
check_int,
|
||||
|
@ -104,15 +106,28 @@ def check_last_owner(user_profile: UserProfile) -> bool:
|
|||
return user_profile.is_realm_owner and not user_profile.is_bot and len(owners) == 1
|
||||
|
||||
|
||||
@has_request_variables
|
||||
def deactivate_user_backend(
|
||||
request: HttpRequest, user_profile: UserProfile, user_id: int
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
user_id: int,
|
||||
deactivation_notification_comment: Optional[str] = REQ(
|
||||
str_validator=check_capped_string(max_length=2000), default=None
|
||||
),
|
||||
) -> HttpResponse:
|
||||
target = access_user_by_id(user_profile, user_id, for_admin=True)
|
||||
if target.is_realm_owner and not user_profile.is_realm_owner:
|
||||
raise OrganizationOwnerRequired()
|
||||
if check_last_owner(target):
|
||||
raise JsonableError(_("Cannot deactivate the only organization owner"))
|
||||
return _deactivate_user_profile_backend(request, user_profile, target)
|
||||
if deactivation_notification_comment is not None:
|
||||
deactivation_notification_comment = deactivation_notification_comment.strip()
|
||||
return _deactivate_user_profile_backend(
|
||||
request,
|
||||
user_profile,
|
||||
target,
|
||||
deactivation_notification_comment=deactivation_notification_comment,
|
||||
)
|
||||
|
||||
|
||||
def deactivate_user_own_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||
|
@ -129,13 +144,33 @@ def deactivate_bot_backend(
|
|||
request: HttpRequest, user_profile: UserProfile, bot_id: int
|
||||
) -> HttpResponse:
|
||||
target = access_bot_by_id(user_profile, bot_id)
|
||||
return _deactivate_user_profile_backend(request, user_profile, target)
|
||||
return _deactivate_user_profile_backend(
|
||||
request, user_profile, target, deactivation_notification_comment=None
|
||||
)
|
||||
|
||||
|
||||
def _deactivate_user_profile_backend(
|
||||
request: HttpRequest, user_profile: UserProfile, target: UserProfile
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
target: UserProfile,
|
||||
*,
|
||||
deactivation_notification_comment: Optional[str],
|
||||
) -> HttpResponse:
|
||||
do_deactivate_user(target, acting_user=user_profile)
|
||||
|
||||
# It's important that we check for None explicitly here, since ""
|
||||
# encodes sending an email without a custom administrator comment.
|
||||
if deactivation_notification_comment is not None:
|
||||
send_email(
|
||||
"zerver/emails/deactivate",
|
||||
to_user_ids=[target.id],
|
||||
from_address=FromAddress.NOREPLY,
|
||||
context={
|
||||
"deactivation_notification_comment": deactivation_notification_comment,
|
||||
"realm_uri": target.realm.uri,
|
||||
"realm_name": target.realm.name,
|
||||
},
|
||||
)
|
||||
return json_success(request)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue