typeahead: Show @-topic typeahead whenever it might be possible to use.

Earlier, when a topic had less than 15 participants,
the @-topic typeahead was not visible. It should be visible
irrespective of the 'realm_wildcard_mention_policy' setting
when the participant count is not greater than 15.

The participant count for a topic can't always be calculated
accurately in the client as some of the messages might still
be loading.

We show @-topic in the typeahead whenever it might be possible
to use it.

We will give an error later if you aren't allowed to use it.

Fixes #27852.
This commit is contained in:
Prakhar Pratyush 2023-11-24 19:36:42 +05:30 committed by Tim Abbott
parent 590d43f475
commit 451ddf4c84
6 changed files with 99 additions and 22 deletions

View File

@ -14,9 +14,12 @@ import * as compose_pm_pill from "./compose_pm_pill";
import * as compose_state from "./compose_state";
import * as compose_ui from "./compose_ui";
import {$t} from "./i18n";
import * as message_store from "./message_store";
import {page_params} from "./page_params";
import * as peer_data from "./peer_data";
import * as people from "./people";
import * as reactions from "./reactions";
import * as recent_senders from "./recent_senders";
import * as settings_config from "./settings_config";
import * as settings_data from "./settings_data";
import * as stream_data from "./stream_data";
@ -383,6 +386,55 @@ function is_recipient_large_stream() {
);
}
export function topic_participant_count_more_than_threshold(stream_id, topic) {
// Topic participants:
// Users who either sent or reacted to the messages in the topic.
const participant_ids = new Set();
const sender_ids = recent_senders.get_topic_recent_senders(stream_id, topic);
for (const id of sender_ids) {
participant_ids.add(id);
}
// If senders count is greater than threshold, no need to calculate reactors.
if (participant_ids.size > wildcard_mention_threshold) {
return true;
}
for (const sender_id of sender_ids) {
const message_ids = recent_senders.get_topic_message_ids_for_sender(
stream_id,
topic,
sender_id,
);
for (const message_id of message_ids) {
const message = message_store.get(message_id);
if (message) {
const message_reactions = reactions.get_message_reactions(message);
const reactor_ids = message_reactions.flatMap((obj) => obj.user_ids);
for (const id of reactor_ids) {
participant_ids.add(id);
}
if (participant_ids.size > wildcard_mention_threshold) {
return true;
}
}
}
}
return false;
}
function is_recipient_large_topic() {
return (
compose_state.stream_id() &&
topic_participant_count_more_than_threshold(
compose_state.stream_id(),
compose_state.topic(),
)
);
}
function wildcard_mention_policy_authorizes_user() {
if (
page_params.realm_wildcard_mention_policy ===
@ -427,10 +479,14 @@ function wildcard_mention_policy_authorizes_user() {
return !page_params.is_guest;
}
export function wildcard_mention_allowed() {
export function stream_wildcard_mention_allowed() {
return !is_recipient_large_stream() || wildcard_mention_policy_authorizes_user();
}
export function topic_wildcard_mention_allowed() {
return !is_recipient_large_topic() || wildcard_mention_policy_authorizes_user();
}
export function set_wildcard_mention_threshold(value) {
wildcard_mention_threshold = value;
}

View File

@ -387,13 +387,15 @@ function get_wildcard_string(mention) {
}
export function broadcast_mentions() {
if (!compose_validate.wildcard_mention_allowed()) {
return [];
}
const wildcard_mention_array = ["all", "everyone"];
if (compose_state.get_message_type() === "stream") {
wildcard_mention_array.push("stream", "topic");
let wildcard_mention_array = [];
if (compose_state.get_message_type() === "private") {
wildcard_mention_array = ["all", "everyone"];
} else if (compose_validate.stream_wildcard_mention_allowed()) {
wildcard_mention_array = ["all", "everyone", "stream", "topic"];
} else if (compose_validate.topic_wildcard_mention_allowed()) {
wildcard_mention_array = ["topic"];
}
return wildcard_mention_array.map((mention, idx) => ({
special_item_text: `${mention} (${get_wildcard_string(mention)})`,
email: mention,

View File

@ -248,3 +248,11 @@ export function get_pm_recent_senders(user_ids_string) {
pm_senders_info.participants.sort(compare_pm_user_ids_by_recency);
return pm_senders_info;
}
export function get_topic_message_ids_for_sender(stream_id, topic, sender_id) {
const id_tracker = topic_senders?.get(stream_id)?.get(topic)?.get(sender_id);
if (id_tracker === undefined) {
return new Set();
}
return id_tracker.ids;
}

View File

@ -324,7 +324,7 @@ test_ui("get_invalid_recipient_emails", ({override_rewire}) => {
assert.deepEqual(compose_validate.get_invalid_recipient_emails(), []);
});
test_ui("test_wildcard_mention_allowed", ({override_rewire}) => {
test_ui("test_stream_wildcard_mention_allowed", ({override_rewire}) => {
page_params.user_id = me.user_id;
// First, check for large streams (>15 subscribers) where the wildcard mention
@ -335,39 +335,39 @@ test_ui("test_wildcard_mention_allowed", ({override_rewire}) => {
settings_config.wildcard_mention_policy_values.by_everyone.code;
page_params.is_guest = true;
page_params.is_admin = false;
assert.ok(compose_validate.wildcard_mention_allowed());
assert.ok(compose_validate.stream_wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.nobody.code;
page_params.is_admin = true;
assert.ok(!compose_validate.wildcard_mention_allowed());
assert.ok(!compose_validate.stream_wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_members.code;
page_params.is_guest = true;
page_params.is_admin = false;
assert.ok(!compose_validate.wildcard_mention_allowed());
assert.ok(!compose_validate.stream_wildcard_mention_allowed());
page_params.is_guest = false;
assert.ok(compose_validate.wildcard_mention_allowed());
assert.ok(compose_validate.stream_wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_moderators_only.code;
page_params.is_moderator = false;
assert.ok(!compose_validate.wildcard_mention_allowed());
assert.ok(!compose_validate.stream_wildcard_mention_allowed());
page_params.is_moderator = true;
assert.ok(compose_validate.wildcard_mention_allowed());
assert.ok(compose_validate.stream_wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_admins_only.code;
page_params.is_admin = false;
assert.ok(!compose_validate.wildcard_mention_allowed());
assert.ok(!compose_validate.stream_wildcard_mention_allowed());
// TODO: Add a by_admins_only case when we implement stream-level administrators.
page_params.is_admin = true;
assert.ok(compose_validate.wildcard_mention_allowed());
assert.ok(compose_validate.stream_wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_full_members.code;
@ -375,9 +375,9 @@ test_ui("test_wildcard_mention_allowed", ({override_rewire}) => {
person.date_joined = new Date(Date.now());
page_params.realm_waiting_period_threshold = 10;
assert.ok(compose_validate.wildcard_mention_allowed());
assert.ok(compose_validate.stream_wildcard_mention_allowed());
page_params.is_admin = false;
assert.ok(!compose_validate.wildcard_mention_allowed());
assert.ok(!compose_validate.stream_wildcard_mention_allowed());
// Now, check for small streams (<=15 subscribers) where the wildcard mention
// policy doesn't matter; everyone is allowed to use wildcard mentions.
@ -386,7 +386,7 @@ test_ui("test_wildcard_mention_allowed", ({override_rewire}) => {
settings_config.wildcard_mention_policy_values.by_admins_only.code;
page_params.is_admin = false;
page_params.is_guest = true;
assert.ok(compose_validate.wildcard_mention_allowed());
assert.ok(compose_validate.stream_wildcard_mention_allowed());
});
test_ui("validate_stream_message", ({override_rewire, mock_template}) => {

View File

@ -22,7 +22,7 @@ const compose_ui = mock_esm("../src/compose_ui", {
const compose_validate = mock_esm("../src/compose_validate", {
validate_message_length: () => true,
warn_if_topic_resolved: noop,
wildcard_mention_allowed: () => true,
stream_wildcard_mention_allowed: () => true,
});
const input_pill = mock_esm("../src/input_pill");
const message_user_ids = mock_esm("../src/message_user_ids", {
@ -82,10 +82,16 @@ run_test("verify wildcard mentions typeahead for stream message", () => {
assert.equal(mention_stream.special_item_text, "stream (translated: Notify stream)");
assert.equal(mention_topic.special_item_text, "topic (translated: Notify topic)");
compose_validate.wildcard_mention_allowed = () => false;
compose_validate.stream_wildcard_mention_allowed = () => false;
compose_validate.topic_wildcard_mention_allowed = () => true;
const mention_topic_only = ct.broadcast_mentions()[0];
assert.equal(mention_topic_only.full_name, "topic");
compose_validate.stream_wildcard_mention_allowed = () => false;
compose_validate.topic_wildcard_mention_allowed = () => false;
const mentionNobody = ct.broadcast_mentions();
assert.equal(mentionNobody.length, 0);
compose_validate.wildcard_mention_allowed = () => true;
compose_validate.stream_wildcard_mention_allowed = () => true;
});
run_test("verify wildcard mentions typeahead for direct message", () => {

View File

@ -184,6 +184,11 @@ test("process_stream_message", () => {
true,
);
// Messages sent by sender1 in stream1 > topic1
assert.equal(rs.get_topic_message_ids_for_sender(stream1, topic1, sender1).size, 2);
// Messages sent by sender1 in stream1 > topic2
assert.equal(rs.get_topic_message_ids_for_sender(stream1, topic2, sender1).size, 0);
// Same stream, but different topics
const message6 = make_stream_message({
stream_id: stream3,