Skip to content

Commit

Permalink
stream settings: Stream_post_policy field for restricting reactions.
Browse files Browse the repository at this point in the history
Option to stream_post_policy is added for restricting posting and
reacting to admins only. This restricts reaction of emojis to
admins only.

Fixes zulip#12835
  • Loading branch information
sahil839 committed Apr 30, 2020
1 parent bde965a commit aa5c022
Show file tree
Hide file tree
Showing 23 changed files with 197 additions and 29 deletions.
37 changes: 35 additions & 2 deletions frontend_tests/node_tests/stream_data.js
Expand Up @@ -494,8 +494,8 @@ run_test('stream_settings', () => {
assert.equal(sub_rows[2].invite_only, false);

assert.equal(sub_rows[0].history_public_to_subscribers, true);
assert.equal(sub_rows[0].stream_post_policy ===
stream_data.stream_post_policy_values.admins.code, true);
assert.equal(sub_rows[0].stream_post_policy,
stream_data.stream_post_policy_values.admins.code);

const sub = stream_data.get_sub('a');
stream_data.update_stream_privacy(sub, {
Expand Down Expand Up @@ -1073,3 +1073,36 @@ run_test('all_topics_in_cache', () => {
sub.first_message_id = 2;
assert.equal(stream_data.all_topics_in_cache(sub), true);
});

run_test('get_restrict_emoji_reaction', () => {
const general = {
name: 'general',
stream_id: 1,
stream_post_policy: stream_data.stream_post_policy_values.everyone.code,
};

const test = {
name: 'test',
stream_id: 1,
stream_post_policy: stream_data.stream_post_policy_values.admins_can_post_and_react.code,
};

stream_data.add_sub(general);
stream_data.add_sub(test);

page_params.is_admin = false;

let restrict_emoji_reaction = stream_data.get_restrict_emoji_reaction('general');
assert.equal(restrict_emoji_reaction, false);

restrict_emoji_reaction = stream_data.get_restrict_emoji_reaction('test');
assert.equal(restrict_emoji_reaction, true);

page_params.is_admin = true;

restrict_emoji_reaction = stream_data.get_restrict_emoji_reaction('general');
assert.equal(restrict_emoji_reaction, false);

restrict_emoji_reaction = stream_data.get_restrict_emoji_reaction('test');
assert.equal(restrict_emoji_reaction, false);
});
2 changes: 2 additions & 0 deletions frontend_tests/node_tests/stream_events.js
Expand Up @@ -2,10 +2,12 @@ const noop = function () {};
const return_true = function () { return true; };
set_global('$', global.make_zjquery());
set_global('document', 'document-stub');

const _settings_notifications = {
update_page: () => {},
};
set_global('settings_notifications', _settings_notifications);
set_global('current_msg_list', {rerender: noop});

zrequire('people');
zrequire('stream_data');
Expand Down
11 changes: 9 additions & 2 deletions static/js/click_handlers.js
Expand Up @@ -140,7 +140,10 @@ exports.initialize = function () {
e.stopPropagation();
const local_id = $(this).attr('data-reaction-id');
const message_id = rows.get_message_id(this);
reactions.process_reaction_click(message_id, local_id);
const message = current_msg_list.get(message_id);
if (!message.is_stream || !stream_data.get_restrict_emoji_reaction(message.stream)) {
reactions.process_reaction_click(message_id, local_id);
}
$(".tooltip").remove();
});

Expand Down Expand Up @@ -179,7 +182,10 @@ exports.initialize = function () {
const local_id = elem.attr('data-reaction-id');
const message_id = rows.get_message_id(e.currentTarget);
const title = reactions.get_reaction_title_data(message_id, local_id);

const message = current_msg_list.get(message_id);
if (message.is_stream && stream_data.get_restrict_emoji_reaction(message.stream)) {
$(this).closest(".message_reaction").find(".disable-reaction-button").show();
}
elem.tooltip({
title: title,
trigger: 'hover',
Expand All @@ -195,6 +201,7 @@ exports.initialize = function () {
$('#main_div').on('mouseleave', '.message_reaction', function (e) {
e.stopPropagation();
$(e.currentTarget).tooltip('destroy');
$(this).closest(".message_reaction").find(".disable-reaction-button").hide();
});

// DESTROY PERSISTING TOOLTIPS ON HOVER
Expand Down
3 changes: 2 additions & 1 deletion static/js/compose.js
Expand Up @@ -514,7 +514,8 @@ function validate_stream_message_post_policy(stream_name) {
const stream_post_permission_type = stream_data.stream_post_policy_values;
const stream_post_policy = stream_data.get_stream_post_policy(stream_name);

if (stream_post_policy === stream_post_permission_type.admins.code) {
if (stream_post_policy === stream_post_permission_type.admins.code ||
stream_post_policy === stream_post_permission_type.admins_can_post_and_react.code) {
compose_error(i18n.t("Only organization admins are allowed to post to this stream."));
return false;
}
Expand Down
12 changes: 10 additions & 2 deletions static/js/emoji_picker.js
Expand Up @@ -659,14 +659,21 @@ exports.register_click_handlers = function () {

$("#main_div").on("click", ".reaction_button", function (e) {
e.stopPropagation();

const message_id = rows.get_message_id(this);
exports.toggle_emoji_popover(this, message_id);
const message = current_msg_list.get(message_id);
if (!message.is_stream || !stream_data.get_restrict_emoji_reaction(message.stream)) {
exports.toggle_emoji_popover(this, message_id);
}
});

$("#main_div").on("mouseenter", ".reaction_button", function (e) {
e.stopPropagation();

const message_id = rows.get_message_id(this);
const message = current_msg_list.get(message_id);
if (message.is_stream && stream_data.get_restrict_emoji_reaction(message.stream)) {
$(this).find(".disable-emoji-icon").show();
}
const elem = $(e.currentTarget);
const title = i18n.t("Add emoji reaction");
elem.tooltip({
Expand All @@ -681,6 +688,7 @@ exports.register_click_handlers = function () {

$('#main_div').on('mouseleave', '.reaction_button', function (e) {
e.stopPropagation();
$(this).find(".disable-emoji-icon").hide();
$(e.currentTarget).tooltip('hide');
});

Expand Down
2 changes: 2 additions & 0 deletions static/js/message_list_view.js
Expand Up @@ -352,6 +352,8 @@ MessageListView.prototype = {
if (message_container.msg.stream) {
message_container.background_color =
stream_data.get_color(message_container.msg.stream);
message_container.restrict_emoji_reaction =
stream_data.get_restrict_emoji_reaction(message_container.msg.stream);
}

message_container.contains_mention = message_container.msg.mentioned;
Expand Down
12 changes: 11 additions & 1 deletion static/js/popovers.js
Expand Up @@ -115,6 +115,16 @@ function calculate_info_popover_placement(size, elt) {
}
}

function show_add_reaction_option(message) {
if (!message.sent_by_me) {
return false;
}
if (message.stream && stream_data.get_restrict_emoji_reaction(message.stream)) {
return false;
}
return true;
}

function get_custom_profile_field_data(user, field, field_types, dateFormat) {
const field_value = people.get_custom_profile_data(user.user_id, field.id);
const field_type = field.type;
Expand Down Expand Up @@ -485,7 +495,7 @@ exports.toggle_actions_popover = function (element, id) {
can_unmute_topic: can_unmute_topic,
should_display_collapse: should_display_collapse,
should_display_uncollapse: should_display_uncollapse,
should_display_add_reaction_option: message.sent_by_me,
should_display_add_reaction_option: show_add_reaction_option(message),
should_display_edit_history_option: should_display_edit_history_option,
conversation_time_uri: conversation_time_uri,
narrowed: narrow_state.active(),
Expand Down
13 changes: 13 additions & 0 deletions static/js/stream_data.js
Expand Up @@ -103,6 +103,10 @@ exports.stream_post_policy_values = {
code: 3,
description: i18n.t("Only organization full members can post"),
},
admins_can_post_and_react: {
code: 4,
description: i18n.t("Only organization administrators can post and react"),
},
};

exports.clear_subscriptions = function () {
Expand Down Expand Up @@ -541,6 +545,15 @@ exports.get_stream_post_policy = function (stream_name) {
return sub.stream_post_policy;
};

exports.get_restrict_emoji_reaction = function (stream_name) {
const stream_post_policy = exports.get_stream_post_policy(stream_name);
if (stream_post_policy === exports.stream_post_policy_values.admins_can_post_and_react.code
&& !page_params.is_admin) {
return true;
}
return false;
};

exports.all_topics_in_cache = function (sub) {
// Checks whether this browser's cache of contiguous messages
// (used to locally render narrows) in message_list.all has all
Expand Down
1 change: 1 addition & 0 deletions static/js/stream_events.js
Expand Up @@ -54,6 +54,7 @@ exports.update_property = function (stream_id, property, value, other_values) {
break;
case 'stream_post_policy':
subs.update_stream_post_policy(sub, value);
current_msg_list.rerender();
break;
default:
blueslip.warn("Unexpected subscription property type", {property: property,
Expand Down
9 changes: 8 additions & 1 deletion static/js/stream_ui_updates.js
Expand Up @@ -143,7 +143,14 @@ exports.update_stream_privacy_type_icon = function (sub) {

exports.update_stream_subscription_type_text = function (sub) {
const stream_settings = stream_edit.settings_for_sub(sub);
const html = render_subscription_type(sub);
const template_data = {
invite_only: sub.invite_only,
history_public_to_subscribers: sub.history_public_to_subscribers,
is_web_public: sub.is_web_public,
stream_post_policy: sub.stream_post_policy,
stream_post_policy_values: stream_data.stream_post_policy_values,
};
const html = render_subscription_type(template_data);
if (stream_edit.is_sub_settings_active(sub)) {
stream_settings.find('.subscription-type-text').expectOne().html(html);
}
Expand Down
20 changes: 16 additions & 4 deletions static/styles/reactions.scss
Expand Up @@ -13,12 +13,13 @@
background-color: hsl(0, 0%, 100%);
border: 1px solid hsl(194, 37%, 84%);
border-radius: 4px;
position: relative;

&.reacted {
background-color: hsl(195, 50%, 95%);
}

&:hover {
&:not(.disabled):hover {
border: 1px solid hsl(200, 100%, 40%);
}

Expand Down Expand Up @@ -64,7 +65,7 @@
color: hsl(0, 0%, 33%);
}

&:hover .message_reaction + .reaction_button {
&:not(.disabled):hover .message_reaction + .reaction_button {
visibility: visible;
pointer-events: all;
background-color: hsl(0, 0%, 98%);
Expand All @@ -76,15 +77,15 @@
margin-right: 3px;
}

&:hover i {
&:not(.disabled):hover i {
color: hsl(200, 100%, 40%);
}

&:only-child {
display: none;
}

&:hover {
&:not(.disabled):hover {
border: 1px solid hsl(200, 100%, 40%);
background-color: hsl(195, 50%, 95%);
cursor: pointer;
Expand Down Expand Up @@ -296,3 +297,14 @@
.typeahead .emoji {
top: 2px;
}

.disable-emoji-icon,
.disable-reaction-button {
display: none;
position: absolute;
width: 100%;
height: 2px;
background-color: hsl(0, 100%, 0%);
top: 8px;
left: 0px;
}
2 changes: 1 addition & 1 deletion static/styles/zulip.scss
Expand Up @@ -651,7 +651,7 @@ td.pointer {
display: inline-block;
position: relative;
color: hsl(0, 0%, 73%);
&:hover {
&:not(.disabled):hover {
color: hsl(200, 100%, 40%);
}
}
Expand Down
2 changes: 1 addition & 1 deletion static/templates/message_body.hbs
Expand Up @@ -48,4 +48,4 @@
</div>
<div class="message_expander message_length_controller" title="{{t 'Expand message (-)' }}">{{t "[More...]" }}</div>
<div class="message_condenser message_length_controller" title="{{t 'Condense message (-)' }}">{{t "[Condense message]" }}</div>
<div class="message_reactions">{{> message_reactions }}</div>
<div class="message_reactions {{#restrict_emoji_reaction}}disabled{{/restrict_emoji_reaction}}">{{> message_reactions}}</div>
5 changes: 3 additions & 2 deletions static/templates/message_controls.hbs
Expand Up @@ -4,8 +4,9 @@
{{/if}}

{{#unless msg/sent_by_me}}
<div class="reaction_button">
<i class="fa fa-smile-o" aria-label="{{#tr this}}Add emoji reaction{{/tr}} (:)" role="button" aria-haspopup="true" tabindex="0"></i>
<div class="reaction_button {{#restrict_emoji_reaction}}disabled{{/restrict_emoji_reaction}}">
<i class="fa fa-smile-o {{#restrict_emoji_reaction}}disabled{{/restrict_emoji_reaction}}" aria-label="{{#tr this}}Add emoji reaction{{/tr}} (:)" role="button" aria-haspopup="true" tabindex="0"></i>
<div class="disable-emoji-icon"></div>
</div>
{{/unless}}

Expand Down
3 changes: 2 additions & 1 deletion static/templates/message_reaction.hbs
@@ -1,4 +1,4 @@
<div class="{{this.class}}" aria-label="{{this.label}}" data-reaction-id={{this.local_id}}>
<div class="{{this.class}} {{#restrict_emoji_reaction}}disabled{{/restrict_emoji_reaction}}" aria-label="{{this.label}}" data-reaction-id={{this.local_id}}>
{{#if this.emoji_alt_code}}
<div class="emoji_alt_code">&nbsp:{{this.emoji_name}}:</div>
{{else}}
Expand All @@ -8,5 +8,6 @@
<div class="emoji emoji-{{this.emoji_code}}"></div>
{{/if}}
{{/if}}
<div class="disable-reaction-button"></div>
<div class="message_reaction_count">{{this.count}}</div>
</div>
2 changes: 1 addition & 1 deletion static/templates/message_reactions.hbs
@@ -1,5 +1,5 @@
{{#each this/msg/message_reactions}}
{{> message_reaction}}
{{> message_reaction restrict_emoji_reaction = ../restrict_emoji_reaction}}
{{/each}}
<div class="reaction_button" aria-label="{{t 'Add emoji reaction' }} (:)">
<i class="fa fa-smile-o" role="button" aria-haspopup="true" tabindex="0" aria-label="{{t 'Add emoji reaction' }} (:)"></i>
Expand Down
2 changes: 2 additions & 0 deletions static/templates/subscription_type.hbs
Expand Up @@ -10,6 +10,8 @@
{{/if}}
{{#if (eq stream_post_policy stream_post_policy_values.admins.code)}}
{{t 'Only organization administrators can post.'}}
{{else if (eq stream_post_policy stream_post_policy_values.admins_can_post_and_react.code)}}
{{t 'Only orgainzation administrators can post and react'}}
{{else if (eq stream_post_policy stream_post_policy_values.non_new_members.code)}}
{{t 'Only organization full members can post.'}}
{{else}}
Expand Down
11 changes: 8 additions & 3 deletions zerver/lib/actions.py
Expand Up @@ -3522,11 +3522,14 @@ def do_change_stream_post_policy(stream: Stream, stream_post_policy: int) -> Non
# is_announcement_only property in early 2020, but we send a
# duplicate event for legacy mobile clients that might want the
# data.
is_announcement_only_value = (stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS or
stream.stream_post_policy ==
Stream.STREAM_POST_POLICY_ADMINS_CAN_POST_AND_REACT)
event = dict(
op="update",
type="stream",
property="is_announcement_only",
value=stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS,
value=is_announcement_only_value,
stream_id=stream.id,
name=stream.name,
)
Expand Down Expand Up @@ -4783,7 +4786,8 @@ def gather_subscriptions_helper(user_profile: UserProfile,
# updated for the is_announcement_only -> stream_post_policy
# migration.
stream_dict['is_announcement_only'] = \
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS or \
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS_CAN_POST_AND_REACT

# Add a few computed fields not directly from the data models.
stream_dict['is_old_stream'] = is_old_stream(stream["date_created"])
Expand Down Expand Up @@ -4834,7 +4838,8 @@ def gather_subscriptions_helper(user_profile: UserProfile,
stream["id"], stream["date_created"], recent_traffic)
# Backwards-compatibility addition of removed field.
stream_dict['is_announcement_only'] = \
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS or \
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS_CAN_POST_AND_REACT

if is_public or user_profile.is_realm_admin:
subscribers = subscriber_map[stream["id"]]
Expand Down
9 changes: 6 additions & 3 deletions zerver/lib/streams.py
Expand Up @@ -133,7 +133,8 @@ def access_stream_for_send_message(sender: UserProfile,
elif sender.is_bot and (sender.bot_owner is not None and
sender.bot_owner.is_realm_admin):
pass
elif stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS:
elif (stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS or
stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS_CAN_POST_AND_REACT):
raise JsonableError(_("Only organization administrators can send to this stream."))
elif stream.stream_post_policy == Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS:
if sender.is_bot and (sender.bot_owner is not None and
Expand Down Expand Up @@ -422,8 +423,10 @@ def list_to_streams(streams_raw: Iterable[Mapping[str, Any]],
stream = existing_stream_map.get(stream_name.lower())
if stream is None:
# Non admins cannot create STREAM_POST_POLICY_ADMINS streams.
if ((stream_dict.get("stream_post_policy", False) ==
Stream.STREAM_POST_POLICY_ADMINS) and not user_profile.is_realm_admin):
if (((stream_dict.get("stream_post_policy", False) ==
Stream.STREAM_POST_POLICY_ADMINS) or (stream_dict.get("stream_post_policy", False) ==
Stream.STREAM_POST_POLICY_ADMINS_CAN_POST_AND_REACT))
and not user_profile.is_realm_admin):
member_creating_announcement_only_stream = True
# New members cannot create STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
# unless they are admins who are also new members of the organization.
Expand Down

0 comments on commit aa5c022

Please sign in to comment.