FEATURE: whispers available for groups (#17170)

Before, whispers were only available for staff members.

Config has been changed to allow to configure privileged groups with access to whispers. Post migration was added to move from the old setting into the new one.

I considered having a boolean column `whisperer` on user model similar to `admin/moderator` for performance reason. Finally, I decided to keep looking for groups as queries are only done for current user and didn't notice any N+1 queries.
This commit is contained in:
Krzysztof Kotlarek 2022-06-30 10:18:12 +10:00 committed by GitHub
parent f44eb13236
commit 09932738e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 275 additions and 56 deletions

View File

@ -233,6 +233,7 @@ export default Controller.extend({
},
isStaffUser: reads("currentUser.staff"),
whisperer: reads("currentUser.whisperer"),
canUnlistTopic: and("model.creatingTopic", "isStaffUser"),
@ -289,12 +290,12 @@ export default Controller.extend({
return SAVE_LABELS[modelAction];
},
@discourseComputed("isStaffUser", "model.action")
canWhisper(isStaffUser, modelAction) {
@discourseComputed("whisperer", "model.action")
canWhisper(whisperer, modelAction) {
return (
this.siteSettings.enable_whispers &&
isStaffUser &&
Composer.REPLY === modelAction
Composer.REPLY === modelAction &&
whisperer
);
},

View File

@ -19,7 +19,11 @@ import userFixtures from "discourse/tests/fixtures/user-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
acceptance("Composer Actions", function (needs) {
needs.user();
needs.user({
id: 5,
username: "kris",
whisperer: true,
});
needs.settings({
prioritize_username_in_ux: true,
display_name_on_post: false,
@ -78,7 +82,8 @@ acceptance("Composer Actions", function (needs) {
);
});
test("replying to post - toggle_whisper", async function (assert) {
test("replying to post - toggle_whisper for whisperers", async function (assert) {
updateCurrentUser({ admin: false, moderator: false });
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -346,7 +351,13 @@ acceptance("Composer Actions", function (needs) {
test("replying to post as TL3 user", async function (assert) {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ moderator: false, admin: false, trust_level: 3 });
updateCurrentUser({
moderator: false,
admin: false,
trust_level: 3,
whisperer: false,
groups: [{ id: 13, name: "tl3_group" }],
});
await visit("/t/internationalization-localization/280");
await click("article#post_3 button.reply");
await composerActions.expand();
@ -364,7 +375,13 @@ acceptance("Composer Actions", function (needs) {
test("replying to post as TL4 user", async function (assert) {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ moderator: false, admin: false, trust_level: 4 });
updateCurrentUser({
moderator: false,
admin: false,
trust_level: 4,
whisperer: false,
groups: [{ id: 13, name: "tl4_group" }],
});
await visit("/t/internationalization-localization/280");
await click("article#post_3 button.reply");
await composerActions.expand();

View File

@ -26,8 +26,14 @@ import { Promise } from "rsvp";
import sinon from "sinon";
acceptance("Composer", function (needs) {
needs.user();
needs.settings({ enable_whispers: true });
needs.user({
id: 5,
username: "kris",
whisperer: true,
});
needs.settings({
enable_whispers: true,
});
needs.site({ can_tag_topics: true });
needs.pretender((server, helper) => {
server.post("/uploads/lookup-urls", () => {
@ -455,7 +461,7 @@ acceptance("Composer", function (needs) {
);
});
test("Composer can toggle whispers", async function (assert) {
test("Composer can toggle whispers when whisperer user", async function (assert) {
const menu = selectKit(".toolbar-popup-menu-options");
await visit("/t/this-is-a-test-topic/9");

View File

@ -1257,7 +1257,7 @@ class TopicsController < ApplicationController
topic_query.options[:limit] = false
topics = topic_query.filter_private_messages_unread(current_user, filter)
else
topics = TopicQuery.unread_filter(topic_query.joined_topic_user, staff: guardian.is_staff?).listable_topics
topics = TopicQuery.unread_filter(topic_query.joined_topic_user, whisperer: guardian.is_whisperer?).listable_topics
topics = TopicQuery.tracked_filter(topics, current_user.id) if params[:tracked].to_s == "true"
if params[:category_id]

View File

@ -18,6 +18,17 @@ module Roleable
!staff?
end
def whisperer?
@whisperer ||= begin
return false if !SiteSetting.enable_whispers?
return true if staff?
whispers_allowed_group_ids = SiteSetting.whispers_allowed_group_ids
return false if whispers_allowed_group_ids.blank?
return true if whispers_allowed_group_ids.include?(primary_group_id)
group_users&.exists?(group_id: whispers_allowed_group_ids)
end
end
def grant_moderation!
return if moderator
set_permission('moderator', true)
@ -61,6 +72,11 @@ module Roleable
end
end
def reload(options = nil)
@whisperer = nil
super(options)
end
private
def auto_approve_user

View File

@ -12,7 +12,7 @@ module TopicTrackingStatePublishable
notification_level: nil)
highest_post_number = DB.query_single(
"SELECT #{user.staff? ? "highest_staff_post_number" : "highest_post_number"} FROM topics WHERE id = ?",
"SELECT #{user.whisperer? ? "highest_staff_post_number" : "highest_post_number"} FROM topics WHERE id = ?",
topic_id
).first

View File

@ -24,7 +24,9 @@ class GroupUser < ActiveRecord::Base
end
def self.update_first_unread_pm(last_seen, limit: 10_000)
DB.exec(<<~SQL, archetype: Archetype.private_message, last_seen: last_seen, limit: limit, now: 10.minutes.ago)
whisperers_group_ids = SiteSetting.whispers_allowed_group_ids
DB.exec(<<~SQL, archetype: Archetype.private_message, last_seen: last_seen, limit: limit, now: 10.minutes.ago, whisperers_group_ids: whisperers_group_ids)
UPDATE group_users gu
SET first_unread_pm_at = Y.min_date
FROM (
@ -51,7 +53,7 @@ class GroupUser < ActiveRecord::Base
WHERE t.deleted_at IS NULL
AND t.archetype = :archetype
AND tu.last_read_post_number < CASE
WHEN u.admin OR u.moderator
WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? 'OR gu2.group_id IN (:whisperers_group_ids)' : ''}
THEN t.highest_staff_post_number
ELSE t.highest_post_number
END

View File

@ -60,7 +60,7 @@ class PostTiming < ActiveRecord::Base
def self.destroy_last_for(user, topic_id: nil, topic: nil)
topic ||= Topic.find(topic_id)
post_number = user.staff? ? topic.highest_staff_post_number : topic.highest_post_number
post_number = user.whisperer? ? topic.highest_staff_post_number : topic.highest_post_number
last_read = post_number - 1

View File

@ -61,7 +61,7 @@ class PrivateMessageTopicTrackingState
SQL
<<~SQL
#{TopicTrackingState.unread_filter_sql(staff: user.staff?)}
#{TopicTrackingState.unread_filter_sql(whisperer: user.whisperer?)}
#{first_unread_pm_at ? "AND topics.updated_at > '#{first_unread_pm_at}'" : ""}
SQL
end
@ -79,7 +79,7 @@ class PrivateMessageTopicTrackingState
u.id AS user_id,
last_read_post_number,
tu.notification_level,
#{TopicTrackingState.highest_post_number_column_select(user.staff?)},
#{TopicTrackingState.highest_post_number_column_select(user.whisperer?)},
ARRAY(SELECT group_id FROM topic_allowed_groups WHERE topic_allowed_groups.topic_id = topics.id) AS group_ids
FROM topics
JOIN users u on u.id = #{user.id.to_i}

View File

@ -191,6 +191,14 @@ class SiteSetting < ActiveRecord::Base
SiteSetting::Upload
end
def self.whispers_allowed_group_ids
if SiteSetting.enable_whispers && SiteSetting.whispers_allowed_groups.present?
SiteSetting.whispers_allowed_groups.split("|").map(&:to_i)
else
[]
end
end
def self.require_invite_code
invite_code.present?
end

View File

@ -404,7 +404,7 @@ class Topic < ActiveRecord::Base
types = Post.types
result = [types[:regular]]
result += [types[:moderator_action], types[:small_action]] if include_moderator_actions
result << types[:whisper] if viewed_by&.staff?
result << types[:whisper] if viewed_by&.whisperer?
result
end

View File

@ -65,7 +65,7 @@ class TopicTrackingState
publish_read(topic.id, 1, topic.user)
end
def self.publish_latest(topic, staff_only = false)
def self.publish_latest(topic, whisper = false)
return unless topic.regular?
tag_ids, tags = nil
@ -89,8 +89,8 @@ class TopicTrackingState
end
group_ids =
if staff_only
[Group::AUTO_GROUPS[:staff]]
if whisper
[Group::AUTO_GROUPS[:staff], *SiteSetting.whispers_allowed_group_ids]
else
topic.category && topic.category.secure_group_ids
end
@ -151,7 +151,7 @@ class TopicTrackingState
group_ids =
if post.post_type == Post.types[:whisper]
[Group::AUTO_GROUPS[:staff]]
[Group::AUTO_GROUPS[:staff], *SiteSetting.whispers_allowed_group_ids]
else
post.topic.category && post.topic.category.secure_group_ids
end
@ -253,8 +253,8 @@ class TopicTrackingState
" AND dismissed_topic_users.id IS NULL"
end
def self.unread_filter_sql(staff: false)
TopicQuery.unread_filter(Topic, staff: staff).where_clause.ast.to_sql
def self.unread_filter_sql(whisperer: false)
TopicQuery.unread_filter(Topic, whisperer: whisperer).where_clause.ast.to_sql
end
def self.treat_as_new_topic_clause
@ -319,6 +319,7 @@ class TopicTrackingState
skip_order: true,
staff: user.staff?,
admin: user.admin?,
whisperer: user.whisperer?,
user: user,
muted_tag_ids: tag_ids
)
@ -332,6 +333,7 @@ class TopicTrackingState
staff: user.staff?,
filter_old_unread: true,
admin: user.admin?,
whisperer: user.whisperer?,
user: user,
muted_tag_ids: tag_ids
)
@ -369,6 +371,7 @@ class TopicTrackingState
skip_order: false,
staff: false,
admin: false,
whisperer: false,
select: nil,
custom_state_filter: nil,
additional_join_sql: nil
@ -377,7 +380,7 @@ class TopicTrackingState
if skip_unread
"1=0"
else
unread_filter_sql(staff: staff)
unread_filter_sql(whisperer: whisperer)
end
filter_old_unread_sql =
@ -399,7 +402,7 @@ class TopicTrackingState
u.id as user_id,
topics.created_at,
topics.updated_at,
#{highest_post_number_column_select(staff)},
#{highest_post_number_column_select(whisperer)},
last_read_post_number,
c.id as category_id,
tu.notification_level,
@ -497,8 +500,8 @@ class TopicTrackingState
sql
end
def self.highest_post_number_column_select(staff)
"#{staff ? "topics.highest_staff_post_number AS highest_post_number" : "topics.highest_post_number"}"
def self.highest_post_number_column_select(whisperer)
"#{whisperer ? "topics.highest_staff_post_number AS highest_post_number" : "topics.highest_post_number"}"
end
def self.publish_read_indicator_on_write(topic_id, last_read_post_number, user_id)

View File

@ -19,7 +19,9 @@ class UserStat < ActiveRecord::Base
UPDATE_UNREAD_USERS_LIMIT = 10_000
def self.update_first_unread_pm(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT)
DB.exec(<<~SQL, archetype: Archetype.private_message, now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, last_seen: last_seen, limit: limit)
whisperers_group_ids = SiteSetting.whispers_allowed_group_ids
DB.exec(<<~SQL, archetype: Archetype.private_message, now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, last_seen: last_seen, limit: limit, whisperers_group_ids: whisperers_group_ids)
UPDATE user_stats us
SET first_unread_pm_at = COALESCE(Z.min_date, :now)
FROM (
@ -35,10 +37,11 @@ class UserStat < ActiveRecord::Base
INNER JOIN topics t ON t.id = tau.topic_id
INNER JOIN users u ON u.id = tau.user_id
LEFT JOIN topic_users tu ON t.id = tu.topic_id AND tu.user_id = tau.user_id
#{whisperers_group_ids.present? ? 'LEFT JOIN group_users gu ON gu.group_id IN (:whisperers_group_ids) AND gu.user_id = u.id' : ''}
WHERE t.deleted_at IS NULL
AND t.archetype = :archetype
AND tu.last_read_post_number < CASE
WHEN u.admin OR u.moderator
WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? 'OR gu.id IS NOT NULL' : ''}
THEN t.highest_staff_post_number
ELSE t.highest_post_number
END

View File

@ -12,6 +12,7 @@ class CurrentUserSerializer < BasicUserSerializer
:notification_channel_position,
:moderator?,
:staff?,
:whisperer?,
:title,
:any_posts,
:enable_quoting,

View File

@ -54,7 +54,7 @@ class ListableTopicSerializer < BasicTopicSerializer
end
def highest_post_number
(scope.is_staff? && object.highest_staff_post_number) || object.highest_post_number
(scope.is_whisperer? && object.highest_staff_post_number) || object.highest_post_number
end
def liked

View File

@ -48,7 +48,7 @@ class UserPostTopicBookmarkBaseSerializer < UserBookmarkBaseSerializer
end
def highest_post_number
scope.is_staff? ? topic.highest_staff_post_number : topic.highest_post_number
scope.is_whisperer? ? topic.highest_staff_post_number : topic.highest_post_number
end
def last_read_post_number

View File

@ -1683,6 +1683,7 @@ en:
enable_badges: "Enable the badge system"
max_favorite_badges: "Maximum number of badges that user can select"
enable_whispers: "Allow staff private communication within topics."
whispers_allowed_groups: "Allow private communication within topics for members of specified groups."
allow_index_in_robots_txt: "Specify in robots.txt that this site is allowed to be indexed by web search engines. In exceptional cases you can permanently <a href='%{base_path}/admin/customize/robots'>override robots.txt</a>."
blocked_email_domains: "A pipe-delimited list of email domains that users are not allowed to register accounts with. Example: mailinator.com|trashmail.net"

View File

@ -323,6 +323,13 @@ basic:
enable_whispers:
client: true
default: false
whispers_allowed_groups:
client: true
type: group_list
list_type: compact
default: ""
allow_any: false
refresh: true
enable_bookmarks_with_reminders:
client: true
default: true

View File

@ -56,6 +56,9 @@ class Guardian
def topic_create_allowed_category_ids
[]
end
def groups
[]
end
def has_trust_level?(level)
false
end
@ -65,6 +68,9 @@ class Guardian
def email
nil
end
def whisperer?
false
end
end
attr_reader :request
@ -99,6 +105,10 @@ class Guardian
@user.moderator?
end
def is_whisperer?
@user.whisperer?
end
def is_category_group_moderator?(category)
return false unless category
return false unless authenticated?

View File

@ -36,11 +36,11 @@ module TopicGuardian
end
def can_create_whisper?
is_staff? && SiteSetting.enable_whispers?
@user.whisperer?
end
def can_see_whispers?(_topic)
is_staff?
def can_see_whispers?(_topic = nil)
@user.whisperer?
end
def can_publish_topic?(topic, category)

View File

@ -336,8 +336,8 @@ class TopicQuery
.where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking])
end
def self.unread_filter(list, staff: false)
col_name = staff ? "highest_staff_post_number" : "highest_post_number"
def self.unread_filter(list, whisperer: false)
col_name = whisperer ? "highest_staff_post_number" : "highest_post_number"
list
.where("tu.last_read_post_number < topics.#{col_name}")
@ -474,7 +474,7 @@ class TopicQuery
def unseen_results(options = {})
result = default_results(options)
result = unseen_filter(result, @user.first_seen_at, @user.staff?) if @user
result = unseen_filter(result, @user.first_seen_at, @user.whisperer?) if @user
result = remove_muted(result, @user, options)
result = apply_shared_drafts(result, get_category_id(options[:category]), options)
@ -489,7 +489,7 @@ class TopicQuery
def unread_results(options = {})
result = TopicQuery.unread_filter(
default_results(options.reverse_merge(unordered: true)),
staff: @user&.staff?)
whisperer: @user&.whisperer?)
.order('CASE WHEN topics.user_id = tu.user_id THEN 1 ELSE 2 END')
if @user
@ -948,7 +948,7 @@ class TopicQuery
def unread_messages(params)
query = TopicQuery.unread_filter(
messages_for_groups_or_user(params[:my_group_ids]),
staff: @user.staff?
whisperer: @user.whisperer?
)
first_unread_pm_at =
@ -1084,10 +1084,10 @@ class TopicQuery
private
def unseen_filter(list, user_first_seen_at, staff)
def unseen_filter(list, user_first_seen_at, whisperer)
list = list.where("topics.bumped_at >= ?", user_first_seen_at)
col_name = staff ? "highest_staff_post_number" : "highest_post_number"
col_name = whisperer ? "highest_staff_post_number" : "highest_post_number"
list.where("tu.last_read_post_number IS NULL OR tu.last_read_post_number < topics.#{col_name}")
end
end

View File

@ -165,7 +165,7 @@ class TopicQuery
def filter_private_messages_unread(user, type)
list = TopicQuery.unread_filter(
private_messages_for(user, type),
staff: user.staff?
whisperer: user.whisperer?
)
first_unread_pm_at =

View File

@ -69,7 +69,7 @@ class TopicsBulkAction
end
def dismiss_posts
highest_number_source_column = @user.staff? ? 'highest_staff_post_number' : 'highest_post_number'
highest_number_source_column = @user.whisperer? ? 'highest_staff_post_number' : 'highest_post_number'
sql = <<~SQL
UPDATE topic_users tu
SET last_read_post_number = t.#{highest_number_source_column}

View File

@ -14,7 +14,7 @@ class Unread
return 0 if @topic_user.last_read_post_number.blank?
return 0 if do_not_notify?(@topic_user.notification_level)
highest_post_number = @guardian.is_staff? ? @topic.highest_staff_post_number : @topic.highest_post_number
highest_post_number = @guardian.is_whisperer? ? @topic.highest_staff_post_number : @topic.highest_post_number
return 0 if @topic_user.last_read_post_number > highest_post_number

View File

@ -42,7 +42,7 @@ export default Component.extend({
@discourseComputed("model.topic.id", "isReply", "isWhisper")
whisperChannelName(topicId, isReply, isWhisper) {
if (topicId && this.currentUser.staff && (isReply || isWhisper)) {
if (topicId && this.currentUser.whisperer && (isReply || isWhisper)) {
return `/discourse-presence/whisper/${topicId}`;
}
},

View File

@ -15,7 +15,7 @@ import User from "discourse/models/user";
import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance("Discourse Presence Plugin", function (needs) {
needs.user();
needs.user({ whisperer: true });
needs.settings({ enable_whispers: true });
test("Doesn't break topic creation", async function (assert) {

View File

@ -119,7 +119,10 @@ RSpec.describe BookmarkQuery do
context "for a whispered post" do
before do
post_bookmark.bookmarkable.update(post_type: Post.types[:whisper])
SiteSetting.enable_whispers = true
end
fab!(:whisperers_group) { Fabricate(:group) }
context "when the user is moderator" do
it "does return the whispered post" do
user.update!(moderator: true)
@ -132,6 +135,13 @@ RSpec.describe BookmarkQuery do
expect(bookmark_query.list_all.count).to eq(3)
end
end
context "when the user is a member of whisperers group" do
it "returns the whispered post" do
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
user.update!(groups: [whisperers_group])
expect(bookmark_query.list_all.count).to eq(3)
end
end
context "when the user is not staff" do
it "does not return the whispered post" do
expect(bookmark_query.list_all.count).to eq(2)

View File

@ -893,6 +893,8 @@ describe Guardian do
end
it 'respects whispers' do
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{group.id}"
regular_post = post
whisper_post = Fabricate.build(:post, post_type: Post.types[:whisper])
@ -916,6 +918,10 @@ describe Guardian do
admin_guardian = Guardian.new(Fabricate.build(:admin))
expect(admin_guardian.can_see?(regular_post)).to eq(true)
expect(admin_guardian.can_see?(whisper_post)).to eq(true)
whisperer_guardian = Guardian.new(Fabricate(:user, groups: [group]))
expect(whisperer_guardian.can_see?(regular_post)).to eq(true)
expect(whisperer_guardian.can_see?(whisper_post)).to eq(true)
end
end

View File

@ -656,7 +656,7 @@ describe PostDestroyer do
it 'should not set Topic#last_post_user_id to a whisperer' do
post_1 = create_post(topic: post.topic, user: moderator)
whisper_1 = create_post(topic: post.topic, user: Fabricate(:user), post_type: Post.types[:whisper])
create_post(topic: post.topic, user: Fabricate(:user), post_type: Post.types[:whisper])
whisper_2 = create_post(topic: post.topic, user: Fabricate(:user), post_type: Post.types[:whisper])
PostDestroyer.new(admin, whisper_2).destroy

View File

@ -910,7 +910,12 @@ describe Search do
])
end
it 'allows staff to search for whispers' do
it 'allows staff and members of whisperers group to search for whispers' do
whisperers_group = Fabricate(:group)
user = Fabricate(:user)
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
post.update!(post_type: Post.types[:whisper], raw: 'this is a tiger')
results = Search.execute('tiger')
@ -920,6 +925,13 @@ describe Search do
results = Search.execute('tiger', guardian: Guardian.new(admin))
expect(results.posts).to eq([post])
results = Search.execute('tiger', guardian: Guardian.new(user))
expect(results.posts).to eq([])
user.groups << whisperers_group
results = Search.execute('tiger', guardian: Guardian.new(user))
expect(results.posts).to eq([post])
end
end

View File

@ -834,6 +834,9 @@ describe TopicQuery do
end
context 'with whispers' do
before do
SiteSetting.enable_whispers = true
end
it 'correctly shows up in unread for staff' do

View File

@ -343,6 +343,7 @@ RSpec.describe TopicView do
context '.post_counts_by_user' do
it 'returns the two posters with their appropriate counts' do
SiteSetting.enable_whispers = true
Fabricate(:post, topic: topic, user: evil_trout, post_type: Post.types[:whisper])
# Should not be counted
Fabricate(:post, topic: topic, user: evil_trout, post_type: Post.types[:whisper], action_code: 'assign')
@ -480,6 +481,7 @@ RSpec.describe TopicView do
context 'whispers' do
it "handles their visibility properly" do
SiteSetting.enable_whispers = true
p1 = Fabricate(:post, topic: topic, user: evil_trout)
p2 = Fabricate(:post, topic: topic, user: evil_trout, post_type: Post.types[:whisper])
p3 = Fabricate(:post, topic: topic, user: evil_trout)

View File

@ -95,6 +95,7 @@ describe TopicsBulkAction do
context "when the highest_staff_post_number is > highest_post_number for a topic (e.g. whisper is last post)" do
it "dismisses posts" do
SiteSetting.enable_whispers = true
post1 = create_post(user: user)
p = create_post(topic_id: post1.topic_id)
create_post(topic_id: post1.topic_id)

View File

@ -4,8 +4,9 @@ require 'unread'
describe Unread do
let (:user) { Fabricate.build(:user, id: 1) }
let (:topic) do
let(:whisperers_group) { Fabricate(:group) }
let(:user) { Fabricate(:user, id: 1, groups: [whisperers_group]) }
let(:topic) do
Fabricate.build(:topic,
posts_count: 13,
highest_staff_post_number: 15,
@ -26,6 +27,7 @@ describe Unread do
describe 'staff counts' do
it 'should correctly return based on staff post number' do
SiteSetting.enable_whispers = true
user.admin = true
topic_user.last_read_post_number = 13
@ -46,11 +48,20 @@ describe Unread do
end
it 'returns the right unread posts for a staff user' do
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = ""
user.admin = true
topic_user.last_read_post_number = 10
expect(unread.unread_posts).to eq(5)
end
it 'returns the right unread posts for a whisperer user' do
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
topic_user.last_read_post_number = 10
expect(unread.unread_posts).to eq(5)
end
it 'should have 0 unread posts if the user has read more posts than exist (deleted)' do
topic_user.last_read_post_number = 14
expect(unread.unread_posts).to eq(0)

View File

@ -65,6 +65,7 @@ describe PrivateMessageTopicTrackingState do
end
it 'returns the right tracking state when topics contain whispers' do
SiteSetting.enable_whispers = true
TopicUser.find_by(user: user_2, topic: private_message).update!(
last_read_post_number: 1
)

View File

@ -5,7 +5,8 @@ describe Topic do
let(:now) { Time.zone.local(2013, 11, 20, 8, 0) }
fab!(:user) { Fabricate(:user) }
fab!(:user1) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) }
fab!(:whisperers_group) { Fabricate(:group) }
fab!(:user2) { Fabricate(:user, groups: [whisperers_group]) }
fab!(:moderator) { Fabricate(:moderator) }
fab!(:coding_horror) { Fabricate(:coding_horror) }
fab!(:evil_trout) { Fabricate(:evil_trout) }
@ -167,6 +168,11 @@ describe Topic do
context '#visible_post_types' do
let(:types) { Post.types }
before do
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
end
it "returns the appropriate types for anonymous users" do
post_types = Topic.visible_post_types
@ -186,7 +192,16 @@ describe Topic do
end
it "returns the appropriate types for staff users" do
post_types = Topic.visible_post_types(Fabricate.build(:moderator))
post_types = Topic.visible_post_types(moderator)
expect(post_types).to include(types[:regular])
expect(post_types).to include(types[:moderator_action])
expect(post_types).to include(types[:small_action])
expect(post_types).to include(types[:whisper])
end
it "returns the appropriate types for whisperer users" do
post_types = Topic.visible_post_types(user2)
expect(post_types).to include(types[:regular])
expect(post_types).to include(types[:moderator_action])

View File

@ -3,6 +3,7 @@
describe TopicTrackingState do
fab!(:user) { Fabricate(:user) }
fab!(:whisperers_group) { Fabricate(:group) }
let(:post) do
create_post
@ -25,6 +26,21 @@ describe TopicTrackingState do
expect(data["payload"]["archetype"]).to eq(Archetype.default)
end
it "publishes whisper post to staff users and members of whisperers group" do
whisperers_group = Fabricate(:group)
Fabricate(:user, groups: [whisperers_group])
Fabricate(:topic_user_watching, topic: topic, user: user)
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
post.update!(post_type: Post.types[:whisper])
message = MessageBus.track_publish("/latest") do
TopicTrackingState.publish_latest(post.topic, true)
end.first
expect(message.group_ids).to contain_exactly(whisperers_group.id, Group::AUTO_GROUPS[:staff])
end
describe 'private message' do
it 'should not publish any message' do
messages = MessageBus.track_publish do
@ -54,6 +70,7 @@ describe TopicTrackingState do
end
it 'correctly publish read for staff' do
SiteSetting.enable_whispers = true
create_post(
raw: "this is a test post",
topic: post.topic,
@ -118,7 +135,32 @@ describe TopicTrackingState do
expect(message.user_ids).to contain_exactly(other_user.id)
end
it "publishes whisper post to staff users and members of whisperers group" do
whisperers_group = Fabricate(:group)
Fabricate(:topic_user_watching, topic: topic, user: user)
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
post.update!(post_type: Post.types[:whisper])
messages = MessageBus.track_publish("/unread") do
TopicTrackingState.publish_unread(post)
end
expect(messages).to eq([])
user.groups << whisperers_group
other_user.grant_admin!
message = MessageBus.track_publish("/unread") do
TopicTrackingState.publish_unread(post)
end.first
expect(message.user_ids).to contain_exactly(user.id, other_user.id)
expect(message.group_ids).to eq(nil)
end
it "does not publish whisper post to non-staff users" do
SiteSetting.enable_whispers = true
post.update!(post_type: Post.types[:whisper])
messages = MessageBus.track_publish("/unread") do
@ -632,6 +674,7 @@ describe TopicTrackingState do
describe ".report" do
it "correctly reports topics with staff posts" do
SiteSetting.enable_whispers = true
create_post(
raw: "this is a test post",
topic: topic,

View File

@ -2645,4 +2645,37 @@ RSpec.describe User do
expect(result).to be(true)
end
end
describe "#whisperer?" do
before do
SiteSetting.enable_whispers = true
end
it 'returns true for an admin user' do
admin = Fabricate.create(:admin)
expect(admin.whisperer?).to eq(true)
end
it 'returns false for an admin user when whispers are not enabled' do
SiteSetting.enable_whispers = false
admin = Fabricate.create(:admin)
expect(admin.whisperer?).to eq(false)
end
it 'returns true for user belonging to whisperers groups' do
group = Fabricate(:group)
whisperer = Fabricate(:user)
user = Fabricate(:user)
SiteSetting.whispers_allowed_groups = "#{group.id}"
expect(whisperer.whisperer?).to eq(false)
expect(user.whisperer?).to eq(false)
group.add(whisperer)
expect(whisperer.whisperer?).to eq(true)
expect(user.whisperer?).to eq(false)
end
end
end

View File

@ -3890,6 +3890,7 @@ describe UsersController do
end
it "includes all post types for staff members" do
SiteSetting.enable_whispers = true
sign_in(admin)
get "/u/#{admin.username}.json", params: { include_post_count_for: topic.id }

View File

@ -1,12 +1,18 @@
# frozen_string_literal: true
RSpec.describe UserPostBookmarkSerializer do
let(:whisperers_group) { Fabricate(:group) }
let(:user) { Fabricate(:user) }
let(:post) { Fabricate(:post, user: user, topic: topic) }
let(:topic) { Fabricate(:topic) }
let!(:bookmark) { Fabricate(:bookmark, name: 'Test', user: user, bookmarkable: post) }
it "uses the correct highest_post_number column based on whether the user is staff" do
before do
SiteSetting.enable_whispers = true
SiteSetting.whispers_allowed_groups = "#{whisperers_group.id}"
end
it "uses the correct highest_post_number column based on whether the user is whisperer" do
Fabricate(:post, topic: topic)
Fabricate(:post, topic: topic)
Fabricate(:whisper, topic: topic)
@ -16,7 +22,7 @@ RSpec.describe UserPostBookmarkSerializer do
expect(serializer.highest_post_number).to eq(3)
user.update!(admin: true)
user.groups << whisperers_group
expect(serializer.highest_post_number).to eq(4)
end