FEATURE: Show warning if user won't be mentioned (#15339)
The new warnings cover more cases and more accurate. Most of the warnings will be visible only to staff members because otherwise they would leak information about user's preferences.
This commit is contained in:
parent
6626089034
commit
5a8b8f6f1e
|
@ -22,6 +22,7 @@ import {
|
||||||
linkSeenHashtags,
|
linkSeenHashtags,
|
||||||
} from "discourse/lib/link-hashtags";
|
} from "discourse/lib/link-hashtags";
|
||||||
import {
|
import {
|
||||||
|
cannotSee,
|
||||||
fetchUnseenMentions,
|
fetchUnseenMentions,
|
||||||
linkSeenMentions,
|
linkSeenMentions,
|
||||||
} from "discourse/lib/link-mentions";
|
} from "discourse/lib/link-mentions";
|
||||||
|
@ -540,7 +541,7 @@ export default Component.extend(ComposerUploadUppy, {
|
||||||
`.mention.cannot-see[data-name="${name}"]`
|
`.mention.cannot-see[data-name="${name}"]`
|
||||||
)?.length > 0
|
)?.length > 0
|
||||||
) {
|
) {
|
||||||
this.cannotSeeMention([{ name }]);
|
this.cannotSeeMention([{ name, reason: cannotSee[name] }]);
|
||||||
found.push(name);
|
found.push(name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -696,16 +696,12 @@ export default Controller.extend({
|
||||||
|
|
||||||
cannotSeeMention(mentions) {
|
cannotSeeMention(mentions) {
|
||||||
mentions.forEach((mention) => {
|
mentions.forEach((mention) => {
|
||||||
const translation = this.get("model.topic.isPrivateMessage")
|
|
||||||
? "composer.cannot_see_mention.private"
|
|
||||||
: "composer.cannot_see_mention.category";
|
|
||||||
const body = I18n.t(translation, {
|
|
||||||
username: `@${mention.name}`,
|
|
||||||
});
|
|
||||||
this.appEvents.trigger("composer-messages:create", {
|
this.appEvents.trigger("composer-messages:create", {
|
||||||
extraClass: "custom-body",
|
extraClass: "custom-body",
|
||||||
templateName: "custom-body",
|
templateName: "custom-body",
|
||||||
body,
|
body: I18n.t(`composer.cannot_see_mention.${mention.reason}`, {
|
||||||
|
username: mention.name,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,7 +46,7 @@ const found = {};
|
||||||
const foundGroups = {};
|
const foundGroups = {};
|
||||||
const mentionableGroups = {};
|
const mentionableGroups = {};
|
||||||
const checked = {};
|
const checked = {};
|
||||||
const cannotSee = [];
|
export const cannotSee = {};
|
||||||
|
|
||||||
function updateFound(mentions, usernames) {
|
function updateFound(mentions, usernames) {
|
||||||
mentions.forEach((mention, index) => {
|
mentions.forEach((mention, index) => {
|
||||||
|
@ -101,7 +101,9 @@ export function fetchUnseenMentions(usernames, topic_id) {
|
||||||
r.valid.forEach((v) => (found[v] = true));
|
r.valid.forEach((v) => (found[v] = true));
|
||||||
r.valid_groups.forEach((vg) => (foundGroups[vg] = true));
|
r.valid_groups.forEach((vg) => (foundGroups[vg] = true));
|
||||||
r.mentionable_groups.forEach((mg) => (mentionableGroups[mg.name] = mg));
|
r.mentionable_groups.forEach((mg) => (mentionableGroups[mg.name] = mg));
|
||||||
r.cannot_see.forEach((cs) => (cannotSee[cs] = true));
|
Object.entries(r.cannot_see).forEach(
|
||||||
|
([username, reason]) => (cannotSee[username] = reason)
|
||||||
|
);
|
||||||
maxGroupMention = r.max_users_notified_per_group_mention;
|
maxGroupMention = r.max_users_notified_per_group_mention;
|
||||||
usernames.forEach((u) => (checked[u] = true));
|
usernames.forEach((u) => (checked[u] = true));
|
||||||
return r;
|
return r;
|
||||||
|
|
|
@ -477,7 +477,7 @@ class UsersController < ApplicationController
|
||||||
usernames = params[:usernames] if params[:usernames].present?
|
usernames = params[:usernames] if params[:usernames].present?
|
||||||
usernames = [params[:username]] if params[:username].present?
|
usernames = [params[:username]] if params[:username].present?
|
||||||
|
|
||||||
raise Discourse::InvalidParameters.new(:usernames) if !usernames.kind_of?(Array)
|
raise Discourse::InvalidParameters.new(:usernames) if !usernames.kind_of?(Array) || usernames.size > 20
|
||||||
|
|
||||||
groups = Group.where(name: usernames).pluck(:name)
|
groups = Group.where(name: usernames).pluck(:name)
|
||||||
mentionable_groups =
|
mentionable_groups =
|
||||||
|
@ -496,15 +496,53 @@ class UsersController < ApplicationController
|
||||||
usernames -= groups
|
usernames -= groups
|
||||||
usernames.each(&:downcase!)
|
usernames.each(&:downcase!)
|
||||||
|
|
||||||
cannot_see = []
|
users = User
|
||||||
|
.where(staged: false, username_lower: usernames)
|
||||||
|
.index_by(&:username_lower)
|
||||||
|
|
||||||
|
cannot_see = {}
|
||||||
here_count = nil
|
here_count = nil
|
||||||
|
|
||||||
topic_id = params[:topic_id]
|
topic_id = params[:topic_id]
|
||||||
if topic_id.present? && topic = Topic.find_by(id: topic_id)
|
if topic_id.present? && topic = Topic.find_by(id: topic_id)
|
||||||
usernames.each do |username|
|
topic_muted_by = TopicUser
|
||||||
if !Guardian.new(User.find_by_username(username)).can_see?(topic)
|
.where(topic: topic)
|
||||||
cannot_see.push(username)
|
.where(user_id: users.values.map(&:id))
|
||||||
|
.where(notification_level: TopicUser.notification_levels[:muted])
|
||||||
|
.pluck(:user_id)
|
||||||
|
.to_set
|
||||||
|
|
||||||
|
if topic.private_message?
|
||||||
|
topic_allowed_user_ids = TopicAllowedUser
|
||||||
|
.where(topic: topic)
|
||||||
|
.where(user_id: users.values.map(&:id))
|
||||||
|
.pluck(:user_id)
|
||||||
|
.to_set
|
||||||
|
|
||||||
|
topic_allowed_group_ids = TopicAllowedGroup
|
||||||
|
.where(topic: topic)
|
||||||
|
.pluck(:group_id)
|
||||||
|
.to_set
|
||||||
end
|
end
|
||||||
|
|
||||||
|
usernames.each do |username|
|
||||||
|
user = users[username]
|
||||||
|
next if user.blank?
|
||||||
|
|
||||||
|
cannot_see_reason = nil
|
||||||
|
if !user.guardian.can_see?(topic)
|
||||||
|
cannot_see_reason = topic.private_message? ? :private : :category
|
||||||
|
elsif topic_muted_by.include?(user.id)
|
||||||
|
cannot_see_reason = :muted_topic
|
||||||
|
elsif topic.private_message? && !topic_allowed_user_ids.include?(user.id) && !user.group_ids.any? { |group_id| topic_allowed_group_ids.include?(group_id) }
|
||||||
|
cannot_see_reason = :not_allowed
|
||||||
|
end
|
||||||
|
|
||||||
|
if !guardian.is_staff? && cannot_see_reason.present? && cannot_see_reason != :private && cannot_see_reason != :category
|
||||||
|
cannot_see_reason = nil # do not leak private information
|
||||||
|
end
|
||||||
|
|
||||||
|
cannot_see[username] = cannot_see_reason if cannot_see_reason.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
if usernames.include?(SiteSetting.here_mention) && guardian.can_mention_here?
|
if usernames.include?(SiteSetting.here_mention) && guardian.can_mention_here?
|
||||||
|
@ -512,12 +550,8 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
result = User.where(staged: false)
|
|
||||||
.where(username_lower: usernames)
|
|
||||||
.pluck(:username_lower)
|
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
valid: result,
|
valid: users.keys,
|
||||||
valid_groups: groups,
|
valid_groups: groups,
|
||||||
mentionable_groups: mentionable_groups,
|
mentionable_groups: mentionable_groups,
|
||||||
cannot_see: cannot_see,
|
cannot_see: cannot_see,
|
||||||
|
|
|
@ -2110,8 +2110,10 @@ en:
|
||||||
one: "By mentioning %{group}, you are about to notify <a href='%{group_link}'>%{count} person</a> – are you sure?"
|
one: "By mentioning %{group}, you are about to notify <a href='%{group_link}'>%{count} person</a> – are you sure?"
|
||||||
other: "By mentioning %{group}, you are about to notify <a href='%{group_link}'>%{count} people</a> – are you sure?"
|
other: "By mentioning %{group}, you are about to notify <a href='%{group_link}'>%{count} people</a> – are you sure?"
|
||||||
cannot_see_mention:
|
cannot_see_mention:
|
||||||
category: "You mentioned %{username} but they won't be notified because they do not have access to this category. You will need to add them to a group that has access to this category."
|
category: "You mentioned @%{username} but they won't be notified because they do not have access to this category. You will need to add them to a group that has access to this category."
|
||||||
private: "You mentioned %{username} but they won't be notified because they are unable to see this personal message. You will need to invite them to this PM."
|
private: "You mentioned @%{username} but they won't be notified because they are unable to see this personal message. You will need to invite them to this personal message."
|
||||||
|
muted_topic: "You mentioned @%{username} but they won't be notified because they muted this topic."
|
||||||
|
not_allowed: "You mentioned @%{username} but they won't be notified because they were not invited to this topic."
|
||||||
here_mention:
|
here_mention:
|
||||||
one: "By mentioning <b>@%{here}</b>, you are about to notify %{count} user – are you sure?"
|
one: "By mentioning <b>@%{here}</b>, you are about to notify %{count} user – are you sure?"
|
||||||
other: "By mentioning <b>@%{here}</b>, you are about to notify %{count} users – are you sure?"
|
other: "By mentioning <b>@%{here}</b>, you are about to notify %{count} users – are you sure?"
|
||||||
|
|
|
@ -3073,34 +3073,30 @@ describe UsersController do
|
||||||
get "/u/is_local_username.json", params: { username: user1.username }
|
get "/u/is_local_username.json", params: { username: user1.username }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["valid"][0]).to eq(user1.username)
|
||||||
expect(json["valid"][0]).to eq(user1.username)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "finds the group" do
|
it "finds the group" do
|
||||||
sign_in(user1)
|
sign_in(user1)
|
||||||
get "/u/is_local_username.json", params: { username: group.name }
|
get "/u/is_local_username.json", params: { username: group.name }
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["valid_groups"]).to include(group.name)
|
||||||
expect(json["valid_groups"]).to include(group.name)
|
expect(response.parsed_body["mentionable_groups"].find { |g| g['name'] == group.name }).to be_present
|
||||||
expect(json["mentionable_groups"].find { |g| g['name'] == group.name }).to be_present
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "finds unmentionable groups" do
|
it "finds unmentionable groups" do
|
||||||
sign_in(user1)
|
sign_in(user1)
|
||||||
get "/u/is_local_username.json", params: { username: unmentionable.name }
|
get "/u/is_local_username.json", params: { username: unmentionable.name }
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["valid_groups"]).to include(unmentionable.name)
|
||||||
expect(json["valid_groups"]).to include(unmentionable.name)
|
expect(response.parsed_body["mentionable_groups"]).to be_blank
|
||||||
expect(json["mentionable_groups"]).to be_blank
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "supports multiples usernames" do
|
it "supports multiples usernames" do
|
||||||
get "/u/is_local_username.json", params: { usernames: [user1.username, "system"] }
|
get "/u/is_local_username.json", params: { usernames: [user1.username, "system"] }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["valid"]).to contain_exactly(user1.username, "system")
|
||||||
expect(json["valid"].size).to eq(2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "never includes staged accounts" do
|
it "never includes staged accounts" do
|
||||||
|
@ -3109,8 +3105,7 @@ describe UsersController do
|
||||||
get "/u/is_local_username.json", params: { usernames: [staged.username] }
|
get "/u/is_local_username.json", params: { usernames: [staged.username] }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["valid"]).to be_blank
|
||||||
expect(json["valid"].size).to eq(0)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns user who cannot see topic" do
|
it "returns user who cannot see topic" do
|
||||||
|
@ -3121,44 +3116,57 @@ describe UsersController do
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["cannot_see"][user1.username]).to eq("category")
|
||||||
expect(json["cannot_see"].size).to eq(1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "never returns a user who can see the topic" do
|
it "never returns a user who can see the topic" do
|
||||||
Guardian.any_instance.expects(:can_see?).with(topic).returns(true)
|
|
||||||
|
|
||||||
get "/u/is_local_username.json", params: {
|
get "/u/is_local_username.json", params: {
|
||||||
usernames: [user1.username], topic_id: topic.id
|
usernames: [user1.username], topic_id: topic.id
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["cannot_see"]).to be_blank
|
||||||
expect(json["cannot_see"].size).to eq(0)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns user who cannot see a private topic" do
|
it "returns user who cannot see a private topic" do
|
||||||
Guardian.any_instance.expects(:can_see?).with(private_topic).returns(false)
|
|
||||||
|
|
||||||
get "/u/is_local_username.json", params: {
|
get "/u/is_local_username.json", params: {
|
||||||
usernames: [user1.username], topic_id: private_topic.id
|
usernames: [user1.username], topic_id: private_topic.id
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["cannot_see"][user1.username]).to eq("private")
|
||||||
expect(json["cannot_see"].size).to eq(1)
|
end
|
||||||
|
|
||||||
|
it "returns user who was not invited to topic" do
|
||||||
|
sign_in(Fabricate(:admin))
|
||||||
|
|
||||||
|
get "/u/is_local_username.json", params: {
|
||||||
|
usernames: [admin.username], topic_id: private_topic.id
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body["cannot_see"][admin.username]).to eq("not_allowed")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "never returns a user who can see the topic" do
|
it "never returns a user who can see the topic" do
|
||||||
Guardian.any_instance.expects(:can_see?).with(private_topic).returns(true)
|
|
||||||
|
|
||||||
get "/u/is_local_username.json", params: {
|
get "/u/is_local_username.json", params: {
|
||||||
usernames: [allowed_user.username], topic_id: private_topic.id
|
usernames: [allowed_user.username], topic_id: private_topic.id
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
expect(response.parsed_body["cannot_see"]).to be_blank
|
||||||
expect(json["cannot_see"].size).to eq(0)
|
end
|
||||||
|
|
||||||
|
it "returns the appropriate reason why user cannot see the topic" do
|
||||||
|
TopicUser.create!(user_id: user1.id, topic_id: topic.id, notification_level: TopicUser.notification_levels[:muted])
|
||||||
|
|
||||||
|
sign_in(admin)
|
||||||
|
get "/u/is_local_username.json", params: {
|
||||||
|
usernames: [user1.username], topic_id: topic.id
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body["cannot_see"][user1.username]).to eq("muted_topic")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue