DEV: Apply syntax_tree formatting to `plugins/*`
This commit is contained in:
parent
93e2dad656
commit
055310cea4
1
.streerc
1
.streerc
|
@ -5,6 +5,5 @@
|
||||||
--ignore-files=config/*
|
--ignore-files=config/*
|
||||||
--ignore-files=db/*
|
--ignore-files=db/*
|
||||||
--ignore-files=lib/*
|
--ignore-files=lib/*
|
||||||
--ignore-files=plugins/*
|
|
||||||
--ignore-files=script/*
|
--ignore-files=script/*
|
||||||
--ignore-files=spec/*
|
--ignore-files=spec/*
|
||||||
|
|
|
@ -9,7 +9,10 @@ class Chat::Api::CategoryChatablesController < ApplicationController
|
||||||
Group
|
Group
|
||||||
.joins(:category_groups)
|
.joins(:category_groups)
|
||||||
.where(category_groups: { category_id: category.id })
|
.where(category_groups: { category_id: category.id })
|
||||||
.where("category_groups.permission_type IN (?)", [CategoryGroup.permission_types[:full], CategoryGroup.permission_types[:create_post]])
|
.where(
|
||||||
|
"category_groups.permission_type IN (?)",
|
||||||
|
[CategoryGroup.permission_types[:full], CategoryGroup.permission_types[:create_post]],
|
||||||
|
)
|
||||||
.joins("LEFT OUTER JOIN group_users ON groups.id = group_users.group_id")
|
.joins("LEFT OUTER JOIN group_users ON groups.id = group_users.group_id")
|
||||||
.group("groups.id", "groups.name")
|
.group("groups.id", "groups.name")
|
||||||
.pluck("groups.name", "COUNT(group_users.user_id)")
|
.pluck("groups.name", "COUNT(group_users.user_id)")
|
||||||
|
|
|
@ -9,16 +9,17 @@ class Chat::Api::HintsController < ApplicationController
|
||||||
|
|
||||||
raise Discourse::InvalidParameters.new(:mentions) if group_names.blank?
|
raise Discourse::InvalidParameters.new(:mentions) if group_names.blank?
|
||||||
|
|
||||||
visible_groups = Group
|
visible_groups =
|
||||||
.where("LOWER(name) IN (?)", group_names)
|
Group.where("LOWER(name) IN (?)", group_names).visible_groups(current_user).pluck(:name)
|
||||||
.visible_groups(current_user)
|
|
||||||
.pluck(:name)
|
|
||||||
|
|
||||||
mentionable_groups = filter_mentionable_groups(visible_groups)
|
mentionable_groups = filter_mentionable_groups(visible_groups)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
unreachable: visible_groups - mentionable_groups.map(&:name),
|
unreachable: visible_groups - mentionable_groups.map(&:name),
|
||||||
over_members_limit: mentionable_groups.select { |g| g.user_count > SiteSetting.max_users_notified_per_group_mention }.map(&:name),
|
over_members_limit:
|
||||||
|
mentionable_groups
|
||||||
|
.select { |g| g.user_count > SiteSetting.max_users_notified_per_group_mention }
|
||||||
|
.map(&:name),
|
||||||
}
|
}
|
||||||
|
|
||||||
result[:invalid] = (group_names - result[:unreachable]) - result[:over_members_limit]
|
result[:invalid] = (group_names - result[:unreachable]) - result[:over_members_limit]
|
||||||
|
|
|
@ -5,8 +5,9 @@ module Jobs
|
||||||
def execute(args)
|
def execute(args)
|
||||||
return if args[:user_id].nil?
|
return if args[:user_id].nil?
|
||||||
|
|
||||||
ChatMessageDestroyer.new
|
ChatMessageDestroyer.new.destroy_in_batches(
|
||||||
.destroy_in_batches(ChatMessage.with_deleted.where(user_id: args[:user_id]))
|
ChatMessage.with_deleted.where(user_id: args[:user_id]),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,10 +15,9 @@ module Jobs
|
||||||
return unless valid_day_value?(:chat_channel_retention_days)
|
return unless valid_day_value?(:chat_channel_retention_days)
|
||||||
|
|
||||||
ChatMessageDestroyer.new.destroy_in_batches(
|
ChatMessageDestroyer.new.destroy_in_batches(
|
||||||
ChatMessage
|
ChatMessage.in_public_channel.with_deleted.created_before(
|
||||||
.in_public_channel
|
SiteSetting.chat_channel_retention_days.days.ago,
|
||||||
.with_deleted
|
),
|
||||||
.created_before(SiteSetting.chat_channel_retention_days.days.ago)
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -26,10 +25,9 @@ module Jobs
|
||||||
return unless valid_day_value?(:chat_dm_retention_days)
|
return unless valid_day_value?(:chat_dm_retention_days)
|
||||||
|
|
||||||
ChatMessageDestroyer.new.destroy_in_batches(
|
ChatMessageDestroyer.new.destroy_in_batches(
|
||||||
ChatMessage
|
ChatMessage.in_dm_channel.with_deleted.created_before(
|
||||||
.in_dm_channel
|
SiteSetting.chat_dm_retention_days.days.ago,
|
||||||
.with_deleted
|
),
|
||||||
.created_before(SiteSetting.chat_dm_retention_days.days.ago)
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,6 @@ class ChatChannel < ActiveRecord::Base
|
||||||
|
|
||||||
# TODO (martin) Move UpdateUserCountsForChatChannels into here
|
# TODO (martin) Move UpdateUserCountsForChatChannels into here
|
||||||
def self.update_counts
|
def self.update_counts
|
||||||
|
|
||||||
# NOTE: ChatChannel#messages_count is not updated every time
|
# NOTE: ChatChannel#messages_count is not updated every time
|
||||||
# a message is created or deleted in a channel, so it should not
|
# a message is created or deleted in a channel, so it should not
|
||||||
# be displayed in the UI. It is updated eventually via Jobs::ChatPeriodicalUpdates
|
# be displayed in the UI. It is updated eventually via Jobs::ChatPeriodicalUpdates
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
class ChatMessageDestroyer
|
class ChatMessageDestroyer
|
||||||
def destroy_in_batches(chat_messages_query, batch_size: 200)
|
def destroy_in_batches(chat_messages_query, batch_size: 200)
|
||||||
chat_messages_query.in_batches(of: batch_size).each do |relation|
|
chat_messages_query
|
||||||
destroyed_ids = relation.destroy_all.pluck(:id)
|
.in_batches(of: batch_size)
|
||||||
reset_last_read(destroyed_ids)
|
.each do |relation|
|
||||||
delete_flags(destroyed_ids)
|
destroyed_ids = relation.destroy_all.pluck(:id)
|
||||||
end
|
reset_last_read(destroyed_ids)
|
||||||
|
delete_flags(destroyed_ids)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
class SaveChatAllowedGroupsSiteSetting < ActiveRecord::Migration[7.0]
|
class SaveChatAllowedGroupsSiteSetting < ActiveRecord::Migration[7.0]
|
||||||
def up
|
def up
|
||||||
chat_enabled = DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_enabled' AND value = 't'")
|
chat_enabled =
|
||||||
|
DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_enabled' AND value = 't'")
|
||||||
return if chat_enabled.blank?
|
return if chat_enabled.blank?
|
||||||
|
|
||||||
chat_allowed_groups = DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_allowed_groups'")
|
chat_allowed_groups =
|
||||||
|
DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_allowed_groups'")
|
||||||
return if chat_allowed_groups.present?
|
return if chat_allowed_groups.present?
|
||||||
|
|
||||||
# The original default was auto group ID 3 (staff) so we are
|
# The original default was auto group ID 3 (staff) so we are
|
||||||
|
|
|
@ -30,10 +30,14 @@ module Chat::ChatChannelFetcher
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: false)
|
def self.generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: false)
|
||||||
category_channel_sql = Category.post_create_allowed(guardian)
|
category_channel_sql =
|
||||||
.joins("INNER JOIN chat_channels ON chat_channels.chatable_id = categories.id AND chat_channels.chatable_type = 'Category'")
|
Category
|
||||||
.select("chat_channels.id")
|
.post_create_allowed(guardian)
|
||||||
.to_sql
|
.joins(
|
||||||
|
"INNER JOIN chat_channels ON chat_channels.chatable_id = categories.id AND chat_channels.chatable_type = 'Category'",
|
||||||
|
)
|
||||||
|
.select("chat_channels.id")
|
||||||
|
.to_sql
|
||||||
dm_channel_sql = ""
|
dm_channel_sql = ""
|
||||||
if !exclude_dm_channels
|
if !exclude_dm_channels
|
||||||
dm_channel_sql = <<~SQL
|
dm_channel_sql = <<~SQL
|
||||||
|
@ -75,8 +79,7 @@ module Chat::ChatChannelFetcher
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.secured_public_channel_search(guardian, options = {})
|
def self.secured_public_channel_search(guardian, options = {})
|
||||||
allowed_channel_ids =
|
allowed_channel_ids = generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: true)
|
||||||
generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: true)
|
|
||||||
|
|
||||||
channels = ChatChannel.includes(chatable: [:topic_only_relative_url])
|
channels = ChatChannel.includes(chatable: [:topic_only_relative_url])
|
||||||
channels = channels.includes(:chat_channel_archive) if options[:include_archives]
|
channels = channels.includes(:chat_channel_archive) if options[:include_archives]
|
||||||
|
|
|
@ -32,10 +32,11 @@ class Chat::ChatMailer
|
||||||
when_away_frequency = UserOption.chat_email_frequencies[:when_away]
|
when_away_frequency = UserOption.chat_email_frequencies[:when_away]
|
||||||
allowed_group_ids = Chat.allowed_group_ids
|
allowed_group_ids = Chat.allowed_group_ids
|
||||||
|
|
||||||
users = User
|
users =
|
||||||
.joins(:user_option)
|
User
|
||||||
.where(user_options: { chat_enabled: true, chat_email_frequency: when_away_frequency })
|
.joins(:user_option)
|
||||||
.where("users.last_seen_at < ?", 15.minutes.ago)
|
.where(user_options: { chat_enabled: true, chat_email_frequency: when_away_frequency })
|
||||||
|
.where("users.last_seen_at < ?", 15.minutes.ago)
|
||||||
|
|
||||||
if !allowed_group_ids.include?(Group::AUTO_GROUPS[:everyone])
|
if !allowed_group_ids.include?(Group::AUTO_GROUPS[:everyone])
|
||||||
users = users.joins(:groups).where(groups: { id: allowed_group_ids })
|
users = users.joins(:groups).where(groups: { id: allowed_group_ids })
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Chat::ChatNotifier
|
||||||
:send_message_notifications,
|
:send_message_notifications,
|
||||||
chat_message_id: chat_message.id,
|
chat_message_id: chat_message.id,
|
||||||
timestamp: timestamp.iso8601(6),
|
timestamp: timestamp.iso8601(6),
|
||||||
reason: :edit
|
reason: :edit,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class Chat::ChatNotifier
|
||||||
:send_message_notifications,
|
:send_message_notifications,
|
||||||
chat_message_id: chat_message.id,
|
chat_message_id: chat_message.id,
|
||||||
timestamp: timestamp.iso8601(6),
|
timestamp: timestamp.iso8601(6),
|
||||||
reason: :new
|
reason: :new,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -112,8 +112,7 @@ class Chat::ChatNotifier
|
||||||
group_mentions_count = group_name_mentions.length
|
group_mentions_count = group_name_mentions.length
|
||||||
|
|
||||||
skip_notifications =
|
skip_notifications =
|
||||||
(direct_mentions_count + group_mentions_count) >
|
(direct_mentions_count + group_mentions_count) > SiteSetting.max_mentions_per_chat_message
|
||||||
SiteSetting.max_mentions_per_chat_message
|
|
||||||
|
|
||||||
{}.tap do |to_notify|
|
{}.tap do |to_notify|
|
||||||
# The order of these methods is the precedence
|
# The order of these methods is the precedence
|
||||||
|
@ -248,24 +247,21 @@ class Chat::ChatNotifier
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_groups
|
def visible_groups
|
||||||
@visible_groups ||=
|
@visible_groups ||= Group.where("LOWER(name) IN (?)", group_name_mentions).visible_groups(@user)
|
||||||
Group
|
|
||||||
.where("LOWER(name) IN (?)", group_name_mentions)
|
|
||||||
.visible_groups(@user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_group_mentions(to_notify, already_covered_ids, skip)
|
def expand_group_mentions(to_notify, already_covered_ids, skip)
|
||||||
return [] if skip || visible_groups.empty?
|
return [] if skip || visible_groups.empty?
|
||||||
|
|
||||||
mentionable_groups = Group
|
mentionable_groups =
|
||||||
.mentionable(@user, include_public: false)
|
Group.mentionable(@user, include_public: false).where(id: visible_groups.map(&:id))
|
||||||
.where(id: visible_groups.map(&:id))
|
|
||||||
|
|
||||||
mentions_disabled = visible_groups - mentionable_groups
|
mentions_disabled = visible_groups - mentionable_groups
|
||||||
|
|
||||||
too_many_members, mentionable = mentionable_groups.partition do |group|
|
too_many_members, mentionable =
|
||||||
group.user_count > SiteSetting.max_users_notified_per_group_mention
|
mentionable_groups.partition do |group|
|
||||||
end
|
group.user_count > SiteSetting.max_users_notified_per_group_mention
|
||||||
|
end
|
||||||
|
|
||||||
to_notify[:group_mentions_disabled] = mentions_disabled
|
to_notify[:group_mentions_disabled] = mentions_disabled
|
||||||
to_notify[:too_many_members] = too_many_members
|
to_notify[:too_many_members] = too_many_members
|
||||||
|
@ -275,7 +271,9 @@ class Chat::ChatNotifier
|
||||||
reached_by_group =
|
reached_by_group =
|
||||||
chat_users
|
chat_users
|
||||||
.includes(:groups)
|
.includes(:groups)
|
||||||
.joins(:groups).where(groups: mentionable).where.not(id: already_covered_ids)
|
.joins(:groups)
|
||||||
|
.where(groups: mentionable)
|
||||||
|
.where.not(id: already_covered_ids)
|
||||||
|
|
||||||
grouped = group_users_to_notify(reached_by_group)
|
grouped = group_users_to_notify(reached_by_group)
|
||||||
|
|
||||||
|
@ -295,7 +293,13 @@ class Chat::ChatNotifier
|
||||||
end
|
end
|
||||||
|
|
||||||
def notify_creator_of_inaccessible_mentions(to_notify)
|
def notify_creator_of_inaccessible_mentions(to_notify)
|
||||||
inaccessible = to_notify.extract!(:unreachable, :welcome_to_join, :too_many_members, :group_mentions_disabled)
|
inaccessible =
|
||||||
|
to_notify.extract!(
|
||||||
|
:unreachable,
|
||||||
|
:welcome_to_join,
|
||||||
|
:too_many_members,
|
||||||
|
:group_mentions_disabled,
|
||||||
|
)
|
||||||
return if inaccessible.values.all?(&:blank?)
|
return if inaccessible.values.all?(&:blank?)
|
||||||
|
|
||||||
ChatPublisher.publish_inaccessible_mentions(
|
ChatPublisher.publish_inaccessible_mentions(
|
||||||
|
@ -304,7 +308,7 @@ class Chat::ChatNotifier
|
||||||
inaccessible[:unreachable].to_a,
|
inaccessible[:unreachable].to_a,
|
||||||
inaccessible[:welcome_to_join].to_a,
|
inaccessible[:welcome_to_join].to_a,
|
||||||
inaccessible[:too_many_members].to_a,
|
inaccessible[:too_many_members].to_a,
|
||||||
inaccessible[:group_mentions_disabled].to_a
|
inaccessible[:group_mentions_disabled].to_a,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -321,9 +325,7 @@ class Chat::ChatNotifier
|
||||||
to_notify
|
to_notify
|
||||||
.except(:unreachable, :welcome_to_join)
|
.except(:unreachable, :welcome_to_join)
|
||||||
.each do |key, user_ids|
|
.each do |key, user_ids|
|
||||||
to_notify[key] = user_ids.reject do |user_id|
|
to_notify[key] = user_ids.reject { |user_id| screener.ignoring_or_muting_actor?(user_id) }
|
||||||
screener.ignoring_or_muting_actor?(user_id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# :welcome_to_join contains users because it's serialized by MB.
|
# :welcome_to_join contains users because it's serialized by MB.
|
||||||
|
@ -351,11 +353,7 @@ class Chat::ChatNotifier
|
||||||
def notify_watching_users(except: [])
|
def notify_watching_users(except: [])
|
||||||
Jobs.enqueue(
|
Jobs.enqueue(
|
||||||
:chat_notify_watching,
|
:chat_notify_watching,
|
||||||
{
|
{ chat_message_id: @chat_message.id, except_user_ids: except, timestamp: @timestamp },
|
||||||
chat_message_id: @chat_message.id,
|
|
||||||
except_user_ids: except,
|
|
||||||
timestamp: @timestamp,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,11 +22,11 @@ class Chat::DuplicateMessageValidator
|
||||||
|
|
||||||
# Check if the same duplicate message has been posted in the last N seconds by any user
|
# Check if the same duplicate message has been posted in the last N seconds by any user
|
||||||
if !chat_message
|
if !chat_message
|
||||||
.chat_channel
|
.chat_channel
|
||||||
.chat_messages
|
.chat_messages
|
||||||
.where("created_at > ?", matrix[:min_past_seconds].seconds.ago)
|
.where("created_at > ?", matrix[:min_past_seconds].seconds.ago)
|
||||||
.where(message: chat_message.message)
|
.where(message: chat_message.message)
|
||||||
.exists?
|
.exists?
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -85,27 +85,27 @@ module Chat::UserNotificationsExtension
|
||||||
"user_notifications.chat_summary.subject.chat_channel_more",
|
"user_notifications.chat_summary.subject.chat_channel_more",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
channel: channels.first.title,
|
channel: channels.first.title,
|
||||||
count: total_count - 1
|
count: total_count - 1,
|
||||||
)
|
)
|
||||||
elsif channels.size == 1 && dm_users.size == 0
|
elsif channels.size == 1 && dm_users.size == 0
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"user_notifications.chat_summary.subject.chat_channel_1",
|
"user_notifications.chat_summary.subject.chat_channel_1",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
channel: channels.first.title
|
channel: channels.first.title,
|
||||||
)
|
)
|
||||||
elsif channels.size == 1 && dm_users.size == 1
|
elsif channels.size == 1 && dm_users.size == 1
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"user_notifications.chat_summary.subject.chat_channel_and_direct_message",
|
"user_notifications.chat_summary.subject.chat_channel_and_direct_message",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
channel: channels.first.title,
|
channel: channels.first.title,
|
||||||
username: dm_users.first.username
|
username: dm_users.first.username,
|
||||||
)
|
)
|
||||||
elsif channels.size == 2
|
elsif channels.size == 2
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"user_notifications.chat_summary.subject.chat_channel_2",
|
"user_notifications.chat_summary.subject.chat_channel_2",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
channel1: channels.first.title,
|
channel1: channels.first.title,
|
||||||
channel2: channels.second.title
|
channel2: channels.second.title,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -116,21 +116,21 @@ module Chat::UserNotificationsExtension
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_1",
|
"user_notifications.chat_summary.subject.direct_message_from_1",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
username: dm_users.first.username
|
username: dm_users.first.username,
|
||||||
)
|
)
|
||||||
when 2
|
when 2
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_2",
|
"user_notifications.chat_summary.subject.direct_message_from_2",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
username1: dm_users.first.username,
|
username1: dm_users.first.username,
|
||||||
username2: dm_users.second.username
|
username2: dm_users.second.username,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_more",
|
"user_notifications.chat_summary.subject.direct_message_from_more",
|
||||||
email_prefix: @email_prefix,
|
email_prefix: @email_prefix,
|
||||||
username: dm_users.first.username,
|
username: dm_users.first.username,
|
||||||
count: dm_users.size - 1
|
count: dm_users.size - 1,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,10 +32,7 @@ describe Chat::ChatMessageCreator do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
let(:direct_message_channel) do
|
let(:direct_message_channel) do
|
||||||
Chat::DirectMessageChannelCreator.create!(
|
Chat::DirectMessageChannelCreator.create!(acting_user: user1, target_users: [user1, user2])
|
||||||
acting_user: user1,
|
|
||||||
target_users: [user1, user2],
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -135,13 +132,14 @@ describe Chat::ChatMessageCreator do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "publishes a DiscourseEvent for new messages" do
|
it "publishes a DiscourseEvent for new messages" do
|
||||||
events = DiscourseEvent.track_events {
|
events =
|
||||||
Chat::ChatMessageCreator.create(
|
DiscourseEvent.track_events do
|
||||||
chat_channel: public_chat_channel,
|
Chat::ChatMessageCreator.create(
|
||||||
user: user1,
|
chat_channel: public_chat_channel,
|
||||||
content: "this is a message",
|
user: user1,
|
||||||
)
|
content: "this is a message",
|
||||||
}
|
)
|
||||||
|
end
|
||||||
expect(events.map { _1[:event_name] }).to include(:chat_message_created)
|
expect(events.map { _1[:event_name] }).to include(:chat_message_created)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -368,8 +366,8 @@ describe Chat::ChatMessageCreator do
|
||||||
content: "hello @#{admin_group.name}",
|
content: "hello @#{admin_group.name}",
|
||||||
)
|
)
|
||||||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||||||
admin2.chat_mentions.count
|
admin2.chat_mentions.count
|
||||||
}.by(1)
|
}.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't mention users twice if they are direct mentioned and group mentioned" do
|
it "doesn't mention users twice if they are direct mentioned and group mentioned" do
|
||||||
|
@ -380,8 +378,8 @@ describe Chat::ChatMessageCreator do
|
||||||
content: "hello @#{admin_group.name} @#{admin1.username} and @#{admin2.username}",
|
content: "hello @#{admin_group.name} @#{admin1.username} and @#{admin2.username}",
|
||||||
)
|
)
|
||||||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||||||
admin2.chat_mentions.count
|
admin2.chat_mentions.count
|
||||||
}.by(1)
|
}.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates chat mentions for group mentions and direct mentions" do
|
it "creates chat mentions for group mentions and direct mentions" do
|
||||||
|
@ -392,8 +390,8 @@ describe Chat::ChatMessageCreator do
|
||||||
content: "hello @#{admin_group.name} @#{user2.username}",
|
content: "hello @#{admin_group.name} @#{user2.username}",
|
||||||
)
|
)
|
||||||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||||||
admin2.chat_mentions.count
|
admin2.chat_mentions.count
|
||||||
}.by(1).and change { user2.chat_mentions.count }.by(1)
|
}.by(1).and change { user2.chat_mentions.count }.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates chat mentions for group mentions and direct mentions" do
|
it "creates chat mentions for group mentions and direct mentions" do
|
||||||
|
@ -404,10 +402,10 @@ describe Chat::ChatMessageCreator do
|
||||||
content: "hello @#{admin_group.name} @#{user_group.name}",
|
content: "hello @#{admin_group.name} @#{user_group.name}",
|
||||||
)
|
)
|
||||||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||||||
admin2.chat_mentions.count
|
admin2.chat_mentions.count
|
||||||
}.by(1).and change { user2.chat_mentions.count }.by(1).and change {
|
}.by(1).and change { user2.chat_mentions.count }.by(1).and change {
|
||||||
user3.chat_mentions.count
|
user3.chat_mentions.count
|
||||||
}.by(1)
|
}.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't create chat mentions for group mentions where the group is un-mentionable" do
|
it "doesn't create chat mentions for group mentions where the group is un-mentionable" do
|
||||||
|
@ -475,8 +473,8 @@ describe Chat::ChatMessageCreator do
|
||||||
upload_ids: [upload1.id, upload2.id],
|
upload_ids: [upload1.id, upload2.id],
|
||||||
)
|
)
|
||||||
}.to change { ChatUpload.where(upload_id: upload1.id).count }.by(1).and change {
|
}.to change { ChatUpload.where(upload_id: upload1.id).count }.by(1).and change {
|
||||||
ChatUpload.where(upload_id: upload2.id).count
|
ChatUpload.where(upload_id: upload2.id).count
|
||||||
}.by(1)
|
}.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "filters out uploads that weren't uploaded by the user" do
|
it "filters out uploads that weren't uploaded by the user" do
|
||||||
|
|
|
@ -64,11 +64,11 @@ describe Chat::ChatMessageRateLimiter do
|
||||||
limiter.run!
|
limiter.run!
|
||||||
|
|
||||||
expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded).and change {
|
expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded).and change {
|
||||||
UserHistory.where(
|
UserHistory.where(
|
||||||
target_user: user,
|
target_user: user,
|
||||||
acting_user: Discourse.system_user,
|
acting_user: Discourse.system_user,
|
||||||
action: UserHistory.actions[:silence_user],
|
action: UserHistory.actions[:silence_user],
|
||||||
).count
|
).count
|
||||||
}.by(1)
|
}.by(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -76,9 +76,7 @@ end
|
||||||
Fabricator(:chat_upload) do
|
Fabricator(:chat_upload) do
|
||||||
transient :user
|
transient :user
|
||||||
|
|
||||||
user do
|
user { Fabricate(:user) }
|
||||||
Fabricate(:user)
|
|
||||||
end
|
|
||||||
|
|
||||||
chat_message { |attrs| Fabricate(:chat_message, user: attrs[:user]) }
|
chat_message { |attrs| Fabricate(:chat_message, user: attrs[:user]) }
|
||||||
upload { |attrs| Fabricate(:upload, user: attrs[:user]) }
|
upload { |attrs| Fabricate(:upload, user: attrs[:user]) }
|
||||||
|
|
|
@ -219,9 +219,19 @@ martin</div>
|
||||||
channel = Fabricate(:chat_channel)
|
channel = Fabricate(:chat_channel)
|
||||||
message1 = Fabricate(:chat_message, chat_channel: channel, user: post.user)
|
message1 = Fabricate(:chat_message, chat_channel: channel, user: post.user)
|
||||||
message2 = Fabricate(:chat_message, chat_channel: channel, user: post.user)
|
message2 = Fabricate(:chat_message, chat_channel: channel, user: post.user)
|
||||||
md = ChatTranscriptService.new(channel, message2.user, messages_or_ids: [message2.id]).generate_markdown
|
md =
|
||||||
|
ChatTranscriptService.new(
|
||||||
|
channel,
|
||||||
|
message2.user,
|
||||||
|
messages_or_ids: [message2.id],
|
||||||
|
).generate_markdown
|
||||||
message1.update!(message: md)
|
message1.update!(message: md)
|
||||||
md_for_post = ChatTranscriptService.new(channel, message1.user, messages_or_ids: [message1.id]).generate_markdown
|
md_for_post =
|
||||||
|
ChatTranscriptService.new(
|
||||||
|
channel,
|
||||||
|
message1.user,
|
||||||
|
messages_or_ids: [message1.id],
|
||||||
|
).generate_markdown
|
||||||
post.update!(raw: md_for_post)
|
post.update!(raw: md_for_post)
|
||||||
expect(post.cooked.chomp).to eq(<<~COOKED.chomp)
|
expect(post.cooked.chomp).to eq(<<~COOKED.chomp)
|
||||||
<div class="chat-transcript" data-message-id="#{message1.id}" data-username="#{message1.user.username}" data-datetime="#{message1.created_at.iso8601}" data-channel-name="#{channel.name}" data-channel-id="#{channel.id}">
|
<div class="chat-transcript" data-message-id="#{message1.id}" data-username="#{message1.user.username}" data-datetime="#{message1.created_at.iso8601}" data-channel-name="#{channel.name}" data-channel-id="#{channel.id}">
|
||||||
|
|
|
@ -56,23 +56,23 @@ describe Jobs::ChatChannelDelete do
|
||||||
expect { described_class.new.execute(chat_channel_id: chat_channel.id) }.to change {
|
expect { described_class.new.execute(chat_channel_id: chat_channel.id) }.to change {
|
||||||
IncomingChatWebhook.where(chat_channel_id: chat_channel.id).count
|
IncomingChatWebhook.where(chat_channel_id: chat_channel.id).count
|
||||||
}.by(-1).and change {
|
}.by(-1).and change {
|
||||||
ChatWebhookEvent.where(incoming_chat_webhook_id: @incoming_chat_webhook_id).count
|
ChatWebhookEvent.where(incoming_chat_webhook_id: @incoming_chat_webhook_id).count
|
||||||
}.by(-1).and change { ChatDraft.where(chat_channel: chat_channel).count }.by(
|
}.by(-1).and change { ChatDraft.where(chat_channel: chat_channel).count }.by(
|
||||||
-1,
|
-1,
|
||||||
).and change {
|
).and change {
|
||||||
UserChatChannelMembership.where(chat_channel: chat_channel).count
|
UserChatChannelMembership.where(chat_channel: chat_channel).count
|
||||||
}.by(-3).and change {
|
}.by(-3).and change {
|
||||||
ChatMessageRevision.where(chat_message_id: @message_ids).count
|
ChatMessageRevision.where(chat_message_id: @message_ids).count
|
||||||
}.by(-1).and change {
|
}.by(-1).and change {
|
||||||
ChatMention.where(chat_message_id: @message_ids).count
|
ChatMention.where(chat_message_id: @message_ids).count
|
||||||
}.by(-1).and change {
|
}.by(-1).and change {
|
||||||
ChatUpload.where(chat_message_id: @message_ids).count
|
ChatUpload.where(chat_message_id: @message_ids).count
|
||||||
}.by(-10).and change {
|
}.by(-10).and change {
|
||||||
ChatMessage.where(id: @message_ids).count
|
ChatMessage.where(id: @message_ids).count
|
||||||
}.by(-20).and change {
|
}.by(-20).and change {
|
||||||
ChatMessageReaction.where(
|
ChatMessageReaction.where(
|
||||||
chat_message_id: @message_ids,
|
chat_message_id: @message_ids,
|
||||||
).count
|
).count
|
||||||
}.by(-10)
|
}.by(-10)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe Jobs::SendMessageNotifications do
|
||||||
subject.execute(
|
subject.execute(
|
||||||
chat_message_id: chat_message.id,
|
chat_message_id: chat_message.id,
|
||||||
reason: "invalid",
|
reason: "invalid",
|
||||||
timestamp: 1.minute.ago
|
timestamp: 1.minute.ago,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,32 +29,21 @@ RSpec.describe Jobs::SendMessageNotifications do
|
||||||
Chat::ChatNotifier.any_instance.expects(:notify_new).never
|
Chat::ChatNotifier.any_instance.expects(:notify_new).never
|
||||||
Chat::ChatNotifier.any_instance.expects(:notify_edit).never
|
Chat::ChatNotifier.any_instance.expects(:notify_edit).never
|
||||||
|
|
||||||
subject.execute(
|
subject.execute(chat_message_id: chat_message.id, reason: "new")
|
||||||
chat_message_id: chat_message.id,
|
|
||||||
reason: "new"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "calls notify_new when the reason is 'new'" do
|
it "calls notify_new when the reason is 'new'" do
|
||||||
Chat::ChatNotifier.any_instance.expects(:notify_new).once
|
Chat::ChatNotifier.any_instance.expects(:notify_new).once
|
||||||
Chat::ChatNotifier.any_instance.expects(:notify_edit).never
|
Chat::ChatNotifier.any_instance.expects(:notify_edit).never
|
||||||
|
|
||||||
subject.execute(
|
subject.execute(chat_message_id: chat_message.id, reason: "new", timestamp: 1.minute.ago)
|
||||||
chat_message_id: chat_message.id,
|
|
||||||
reason: "new",
|
|
||||||
timestamp: 1.minute.ago
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "calls notify_edit when the reason is 'edit'" do
|
it "calls notify_edit when the reason is 'edit'" do
|
||||||
Chat::ChatNotifier.any_instance.expects(:notify_new).never
|
Chat::ChatNotifier.any_instance.expects(:notify_new).never
|
||||||
Chat::ChatNotifier.any_instance.expects(:notify_edit).once
|
Chat::ChatNotifier.any_instance.expects(:notify_edit).once
|
||||||
|
|
||||||
subject.execute(
|
subject.execute(chat_message_id: chat_message.id, reason: "edit", timestamp: 1.minute.ago)
|
||||||
chat_message_id: chat_message.id,
|
|
||||||
reason: "edit",
|
|
||||||
timestamp: 1.minute.ago
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -142,17 +142,38 @@ describe Chat::ChatChannelFetcher do
|
||||||
fab!(:group_user) { Fabricate(:group_user, group: group, user: user1) }
|
fab!(:group_user) { Fabricate(:group_user, group: group, user: user1) }
|
||||||
|
|
||||||
it "does not include the category channel for member of group with readonly access" do
|
it "does not include the category channel for member of group with readonly access" do
|
||||||
category_channel.update!(chatable: Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:readonly]))
|
category_channel.update!(
|
||||||
|
chatable:
|
||||||
|
Fabricate(
|
||||||
|
:private_category,
|
||||||
|
group: group,
|
||||||
|
permission_type: CategoryGroup.permission_types[:readonly],
|
||||||
|
),
|
||||||
|
)
|
||||||
expect(subject.all_secured_channel_ids(guardian)).to be_empty
|
expect(subject.all_secured_channel_ids(guardian)).to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it "includes the category channel for member of group with create_post access" do
|
it "includes the category channel for member of group with create_post access" do
|
||||||
category_channel.update!(chatable: Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:create_post]))
|
category_channel.update!(
|
||||||
|
chatable:
|
||||||
|
Fabricate(
|
||||||
|
:private_category,
|
||||||
|
group: group,
|
||||||
|
permission_type: CategoryGroup.permission_types[:create_post],
|
||||||
|
),
|
||||||
|
)
|
||||||
expect(subject.all_secured_channel_ids(guardian)).to match_array([category_channel.id])
|
expect(subject.all_secured_channel_ids(guardian)).to match_array([category_channel.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "includes the category channel for member of group with full access" do
|
it "includes the category channel for member of group with full access" do
|
||||||
category_channel.update!(chatable: Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:full]))
|
category_channel.update!(
|
||||||
|
chatable:
|
||||||
|
Fabricate(
|
||||||
|
:private_category,
|
||||||
|
group: group,
|
||||||
|
permission_type: CategoryGroup.permission_types[:full],
|
||||||
|
),
|
||||||
|
)
|
||||||
expect(subject.all_secured_channel_ids(guardian)).to match_array([category_channel.id])
|
expect(subject.all_secured_channel_ids(guardian)).to match_array([category_channel.id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe Chat::ChatMessageReactor do
|
||||||
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel, user: reacting_user) }
|
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel, user: reacting_user) }
|
||||||
let(:subject) { described_class.new(reacting_user, channel) }
|
let(:subject) { described_class.new(reacting_user, channel) }
|
||||||
|
|
||||||
it 'calls guardian ensure_can_join_chat_channel!' do
|
it "calls guardian ensure_can_join_chat_channel!" do
|
||||||
Guardian.any_instance.expects(:ensure_can_join_chat_channel!).once
|
Guardian.any_instance.expects(:ensure_can_join_chat_channel!).once
|
||||||
subject.react!(message_id: message_1.id, react_action: :add, emoji: ":+1:")
|
subject.react!(message_id: message_1.id, react_action: :add, emoji: ":+1:")
|
||||||
end
|
end
|
||||||
|
|
|
@ -275,7 +275,7 @@ describe Chat::ChatNotifier do
|
||||||
|
|
||||||
include_examples "ensure only channel members are notified"
|
include_examples "ensure only channel members are notified"
|
||||||
|
|
||||||
it 'calls guardian can_join_chat_channel?' do
|
it "calls guardian can_join_chat_channel?" do
|
||||||
Guardian.any_instance.expects(:can_join_chat_channel?).at_least_once
|
Guardian.any_instance.expects(:can_join_chat_channel?).at_least_once
|
||||||
msg = build_cooked_msg("Hello @#{group.name} and @#{user_2.username}", user_1)
|
msg = build_cooked_msg("Hello @#{group.name} and @#{user_2.username}", user_1)
|
||||||
to_notify = described_class.new(msg, msg.created_at).notify_new
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
||||||
|
@ -463,7 +463,8 @@ describe Chat::ChatNotifier do
|
||||||
|
|
||||||
expect(not_participating_msg).to be_present
|
expect(not_participating_msg).to be_present
|
||||||
expect(not_participating_msg.data[:cannot_see]).to be_empty
|
expect(not_participating_msg.data[:cannot_see]).to be_empty
|
||||||
not_participating_users = not_participating_msg.data[:without_membership].map { |u| u["id"] }
|
not_participating_users =
|
||||||
|
not_participating_msg.data[:without_membership].map { |u| u["id"] }
|
||||||
expect(not_participating_users).to contain_exactly(user_3.id)
|
expect(not_participating_users).to contain_exactly(user_3.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -515,7 +516,8 @@ describe Chat::ChatNotifier do
|
||||||
|
|
||||||
expect(not_participating_msg).to be_present
|
expect(not_participating_msg).to be_present
|
||||||
expect(not_participating_msg.data[:cannot_see]).to be_empty
|
expect(not_participating_msg.data[:cannot_see]).to be_empty
|
||||||
not_participating_users = not_participating_msg.data[:without_membership].map { |u| u["id"] }
|
not_participating_users =
|
||||||
|
not_participating_msg.data[:without_membership].map { |u| u["id"] }
|
||||||
expect(not_participating_users).to contain_exactly(user_3.id)
|
expect(not_participating_users).to contain_exactly(user_3.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -539,7 +541,8 @@ describe Chat::ChatNotifier do
|
||||||
|
|
||||||
expect(not_participating_msg).to be_present
|
expect(not_participating_msg).to be_present
|
||||||
expect(not_participating_msg.data[:cannot_see]).to be_empty
|
expect(not_participating_msg.data[:cannot_see]).to be_empty
|
||||||
not_participating_users = not_participating_msg.data[:without_membership].map { |u| u["id"] }
|
not_participating_users =
|
||||||
|
not_participating_msg.data[:without_membership].map { |u| u["id"] }
|
||||||
expect(not_participating_users).to contain_exactly(user_3.id)
|
expect(not_participating_users).to contain_exactly(user_3.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -598,11 +601,12 @@ describe Chat::ChatNotifier do
|
||||||
SiteSetting.max_users_notified_per_group_mention = (group.user_count - 1)
|
SiteSetting.max_users_notified_per_group_mention = (group.user_count - 1)
|
||||||
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/chat/#{channel.id}") do
|
messages =
|
||||||
to_notify = described_class.new(msg, msg.created_at).notify_new
|
MessageBus.track_publish("/chat/#{channel.id}") do
|
||||||
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
||||||
|
|
||||||
expect(to_notify[group.name]).to be_nil
|
expect(to_notify[group.name]).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
too_many_members_msg = messages.first
|
too_many_members_msg = messages.first
|
||||||
expect(too_many_members_msg).to be_present
|
expect(too_many_members_msg).to be_present
|
||||||
|
@ -614,11 +618,12 @@ describe Chat::ChatNotifier do
|
||||||
group.update!(mentionable_level: Group::ALIAS_LEVELS[:only_admins])
|
group.update!(mentionable_level: Group::ALIAS_LEVELS[:only_admins])
|
||||||
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/chat/#{channel.id}") do
|
messages =
|
||||||
to_notify = described_class.new(msg, msg.created_at).notify_new
|
MessageBus.track_publish("/chat/#{channel.id}") do
|
||||||
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
||||||
|
|
||||||
expect(to_notify[group.name]).to be_nil
|
expect(to_notify[group.name]).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
mentions_disabled_msg = messages.first
|
mentions_disabled_msg = messages.first
|
||||||
expect(mentions_disabled_msg).to be_present
|
expect(mentions_disabled_msg).to be_present
|
||||||
|
|
|
@ -46,7 +46,7 @@ describe Chat::ChatReviewQueue do
|
||||||
|
|
||||||
it "returns an error" do
|
it "returns an error" do
|
||||||
expect(second_flag_result).to include success: false,
|
expect(second_flag_result).to include success: false,
|
||||||
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns an error when trying to use notify_moderators and the previous flag is still pending" do
|
it "returns an error when trying to use notify_moderators and the previous flag is still pending" do
|
||||||
|
@ -59,7 +59,7 @@ describe Chat::ChatReviewQueue do
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(notify_moderators_result).to include success: false,
|
expect(notify_moderators_result).to include success: false,
|
||||||
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ describe Chat::ChatReviewQueue do
|
||||||
queue.flag_message(message, admin_guardian, ReviewableScore.types[:spam])
|
queue.flag_message(message, admin_guardian, ReviewableScore.types[:spam])
|
||||||
|
|
||||||
expect(second_flag_result).to include success: false,
|
expect(second_flag_result).to include success: false,
|
||||||
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ describe Chat::ChatReviewQueue do
|
||||||
|
|
||||||
it "raises an error when we are inside the cooldown window" do
|
it "raises an error when we are inside the cooldown window" do
|
||||||
expect(second_flag_result).to include success: false,
|
expect(second_flag_result).to include success: false,
|
||||||
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
errors: [I18n.t("chat.reviewables.message_already_handled")]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "allows the user to re-flag after the cooldown period" do
|
it "allows the user to re-flag after the cooldown period" do
|
||||||
|
|
|
@ -92,17 +92,32 @@ RSpec.describe Chat::GuardianExtensions do
|
||||||
fab!(:group_user) { Fabricate(:group_user, group: group, user: user) }
|
fab!(:group_user) { Fabricate(:group_user, group: group, user: user) }
|
||||||
|
|
||||||
it "returns true if the user can join the category" do
|
it "returns true if the user can join the category" do
|
||||||
category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:readonly])
|
category =
|
||||||
|
Fabricate(
|
||||||
|
:private_category,
|
||||||
|
group: group,
|
||||||
|
permission_type: CategoryGroup.permission_types[:readonly],
|
||||||
|
)
|
||||||
channel.update(chatable: category)
|
channel.update(chatable: category)
|
||||||
guardian = Guardian.new(user)
|
guardian = Guardian.new(user)
|
||||||
expect(guardian.can_join_chat_channel?(channel)).to eq(false)
|
expect(guardian.can_join_chat_channel?(channel)).to eq(false)
|
||||||
|
|
||||||
category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:create_post])
|
category =
|
||||||
|
Fabricate(
|
||||||
|
:private_category,
|
||||||
|
group: group,
|
||||||
|
permission_type: CategoryGroup.permission_types[:create_post],
|
||||||
|
)
|
||||||
channel.update(chatable: category)
|
channel.update(chatable: category)
|
||||||
guardian = Guardian.new(user)
|
guardian = Guardian.new(user)
|
||||||
expect(guardian.can_join_chat_channel?(channel)).to eq(true)
|
expect(guardian.can_join_chat_channel?(channel)).to eq(true)
|
||||||
|
|
||||||
category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:full])
|
category =
|
||||||
|
Fabricate(
|
||||||
|
:private_category,
|
||||||
|
group: group,
|
||||||
|
permission_type: CategoryGroup.permission_types[:full],
|
||||||
|
)
|
||||||
channel.update(chatable: category)
|
channel.update(chatable: category)
|
||||||
guardian = Guardian.new(user)
|
guardian = Guardian.new(user)
|
||||||
expect(guardian.can_join_chat_channel?(channel)).to eq(true)
|
expect(guardian.can_join_chat_channel?(channel)).to eq(true)
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe UserNotifications do
|
||||||
Chat::DirectMessageChannelCreator.create!(acting_user: sender, target_users: [sender, user])
|
Chat::DirectMessageChannelCreator.create!(acting_user: sender, target_users: [sender, user])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calls guardian can_join_chat_channel?' do
|
it "calls guardian can_join_chat_channel?" do
|
||||||
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
||||||
Guardian.any_instance.expects(:can_join_chat_channel?).once
|
Guardian.any_instance.expects(:can_join_chat_channel?).once
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
@ -34,11 +34,12 @@ describe UserNotifications do
|
||||||
|
|
||||||
describe "email subject" do
|
describe "email subject" do
|
||||||
it "includes the sender username in the subject" do
|
it "includes the sender username in the subject" do
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_1",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.direct_message_from_1",
|
||||||
username: sender.username
|
email_prefix: SiteSetting.title,
|
||||||
)
|
username: sender.username,
|
||||||
|
)
|
||||||
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
|
@ -54,11 +55,12 @@ describe UserNotifications do
|
||||||
chat_channel: channel,
|
chat_channel: channel,
|
||||||
)
|
)
|
||||||
DirectMessageUser.create!(direct_message: channel.chatable, user: another_participant)
|
DirectMessageUser.create!(direct_message: channel.chatable, user: another_participant)
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_1",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.direct_message_from_1",
|
||||||
username: sender.username
|
email_prefix: SiteSetting.title,
|
||||||
)
|
username: sender.username,
|
||||||
|
)
|
||||||
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
|
@ -80,12 +82,13 @@ describe UserNotifications do
|
||||||
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
Fabricate(:chat_message, user: sender, chat_channel: channel)
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_2",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.direct_message_from_2",
|
||||||
username1: another_dm_user.username,
|
email_prefix: SiteSetting.title,
|
||||||
username2: sender.username
|
username1: another_dm_user.username,
|
||||||
)
|
username2: sender.username,
|
||||||
|
)
|
||||||
|
|
||||||
expect(email.subject).to eq(expected_subject)
|
expect(email.subject).to eq(expected_subject)
|
||||||
expect(email.subject).to include(sender.username)
|
expect(email.subject).to include(sender.username)
|
||||||
|
@ -116,12 +119,13 @@ describe UserNotifications do
|
||||||
|
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.direct_message_from_more",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.direct_message_from_more",
|
||||||
username: senders.first.username,
|
email_prefix: SiteSetting.title,
|
||||||
count: 2
|
username: senders.first.username,
|
||||||
)
|
count: 2,
|
||||||
|
)
|
||||||
|
|
||||||
expect(email.subject).to eq(expected_subject)
|
expect(email.subject).to eq(expected_subject)
|
||||||
end
|
end
|
||||||
|
@ -162,11 +166,12 @@ describe UserNotifications do
|
||||||
before { Fabricate(:chat_mention, user: user, chat_message: chat_message) }
|
before { Fabricate(:chat_mention, user: user, chat_message: chat_message) }
|
||||||
|
|
||||||
it "includes the sender username in the subject" do
|
it "includes the sender username in the subject" do
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.chat_channel_1",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.chat_channel_1",
|
||||||
channel: channel.title(user)
|
email_prefix: SiteSetting.title,
|
||||||
)
|
channel: channel.title(user),
|
||||||
|
)
|
||||||
|
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
|
@ -193,12 +198,13 @@ describe UserNotifications do
|
||||||
|
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.chat_channel_2",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.chat_channel_2",
|
||||||
channel1: channel.title(user),
|
email_prefix: SiteSetting.title,
|
||||||
channel2: another_chat_channel.title(user)
|
channel1: channel.title(user),
|
||||||
)
|
channel2: another_chat_channel.title(user),
|
||||||
|
)
|
||||||
|
|
||||||
expect(email.subject).to eq(expected_subject)
|
expect(email.subject).to eq(expected_subject)
|
||||||
expect(email.subject).to include(channel.title(user))
|
expect(email.subject).to include(channel.title(user))
|
||||||
|
@ -224,12 +230,13 @@ describe UserNotifications do
|
||||||
Fabricate(:chat_mention, user: user, chat_message: another_chat_message)
|
Fabricate(:chat_mention, user: user, chat_message: another_chat_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.chat_channel_more",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.chat_channel_more",
|
||||||
channel: channel.title(user),
|
email_prefix: SiteSetting.title,
|
||||||
count: 2
|
channel: channel.title(user),
|
||||||
)
|
count: 2,
|
||||||
|
)
|
||||||
|
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
|
@ -250,12 +257,13 @@ describe UserNotifications do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "always includes the DM second" do
|
it "always includes the DM second" do
|
||||||
expected_subject = I18n.t(
|
expected_subject =
|
||||||
"user_notifications.chat_summary.subject.chat_channel_and_direct_message",
|
I18n.t(
|
||||||
email_prefix: SiteSetting.title,
|
"user_notifications.chat_summary.subject.chat_channel_and_direct_message",
|
||||||
channel: channel.title(user),
|
email_prefix: SiteSetting.title,
|
||||||
username: sender.username
|
channel: channel.title(user),
|
||||||
)
|
username: sender.username,
|
||||||
|
)
|
||||||
|
|
||||||
email = described_class.chat_summary(user, {})
|
email = described_class.chat_summary(user, {})
|
||||||
|
|
||||||
|
|
|
@ -517,7 +517,8 @@ describe ChatMessage do
|
||||||
it "keeps the same hashtags the user has permission to after rebake" do
|
it "keeps the same hashtags the user has permission to after rebake" do
|
||||||
group.add(chat_message.user)
|
group.add(chat_message.user)
|
||||||
chat_message.update!(
|
chat_message.update!(
|
||||||
message: "this is the message ##{category.slug} ##{secure_category.slug} ##{chat_message.chat_channel.slug}",
|
message:
|
||||||
|
"this is the message ##{category.slug} ##{secure_category.slug} ##{chat_message.chat_channel.slug}",
|
||||||
)
|
)
|
||||||
chat_message.cook
|
chat_message.cook
|
||||||
chat_message.save!
|
chat_message.save!
|
||||||
|
|
|
@ -11,9 +11,7 @@ describe DeletedChatUser do
|
||||||
|
|
||||||
describe "#avatar_template" do
|
describe "#avatar_template" do
|
||||||
it "returns a default path" do
|
it "returns a default path" do
|
||||||
expect(subject.avatar_template).to eq(
|
expect(subject.avatar_template).to eq("/plugins/chat/images/deleted-chat-user-avatar.png")
|
||||||
"/plugins/chat/images/deleted-chat-user-avatar.png",
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,11 +12,7 @@ RSpec.describe Chat::Api::ChatChannelsCurrentUserNotificationsSettingsController
|
||||||
include_examples "channel access example",
|
include_examples "channel access example",
|
||||||
:put,
|
:put,
|
||||||
"/notifications-settings/me",
|
"/notifications-settings/me",
|
||||||
{
|
{ notifications_settings: { muted: true } }
|
||||||
notifications_settings: {
|
|
||||||
muted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
context "when category channel has invalid params" do
|
context "when category channel has invalid params" do
|
||||||
fab!(:channel_1) { Fabricate(:category_channel) }
|
fab!(:channel_1) { Fabricate(:category_channel) }
|
||||||
|
|
|
@ -61,9 +61,9 @@ RSpec.describe Chat::Api::ChatChannelsStatusController do
|
||||||
|
|
||||||
context "when changing from open to closed" do
|
context "when changing from open to closed" do
|
||||||
it "changes the status" do
|
it "changes the status" do
|
||||||
expect { put "/chat/api/channels/#{channel_1.id}/status", params: status("closed") }.to change {
|
expect {
|
||||||
channel_1.reload.status
|
put "/chat/api/channels/#{channel_1.id}/status", params: status("closed")
|
||||||
}.to("closed").from("open")
|
}.to change { channel_1.reload.status }.to("closed").from("open")
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
channel = response.parsed_body["channel"]
|
channel = response.parsed_body["channel"]
|
||||||
|
@ -75,9 +75,9 @@ RSpec.describe Chat::Api::ChatChannelsStatusController do
|
||||||
before { channel_1.update!(status: "closed") }
|
before { channel_1.update!(status: "closed") }
|
||||||
|
|
||||||
it "changes the status" do
|
it "changes the status" do
|
||||||
expect { put "/chat/api/channels/#{channel_1.id}/status", params: status("open") }.to change {
|
expect {
|
||||||
channel_1.reload.status
|
put "/chat/api/channels/#{channel_1.id}/status", params: status("open")
|
||||||
}.to("open").from("closed")
|
}.to change { channel_1.reload.status }.to("open").from("closed")
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
channel = response.parsed_body["channel"]
|
channel = response.parsed_body["channel"]
|
||||||
|
|
|
@ -1114,7 +1114,11 @@ RSpec.describe Chat::ChatController do
|
||||||
it "returns a 403 if the user can't see the channel" do
|
it "returns a 403 if the user can't see the channel" do
|
||||||
category.update!(read_restricted: true)
|
category.update!(read_restricted: true)
|
||||||
group = Fabricate(:group)
|
group = Fabricate(:group)
|
||||||
CategoryGroup.create(group: group, category: category, permission_type: CategoryGroup.permission_types[:create_post])
|
CategoryGroup.create(
|
||||||
|
group: group,
|
||||||
|
category: category,
|
||||||
|
permission_type: CategoryGroup.permission_types[:create_post],
|
||||||
|
)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
post "/chat/#{channel.id}/quote.json",
|
post "/chat/#{channel.id}/quote.json",
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe CategoriesController do
|
RSpec.describe CategoriesController do
|
||||||
describe '#destroy' do
|
describe "#destroy" do
|
||||||
subject(:destroy_category) { delete "/categories/#{category.slug}.json" }
|
subject(:destroy_category) { delete "/categories/#{category.slug}.json" }
|
||||||
|
|
||||||
fab!(:admin) { Fabricate(:admin) }
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
|
|
@ -23,11 +23,7 @@ RSpec.describe ChatMessageDestroyer do
|
||||||
|
|
||||||
it "deletes flags associated to deleted chat messages" do
|
it "deletes flags associated to deleted chat messages" do
|
||||||
guardian = Guardian.new(Discourse.system_user)
|
guardian = Guardian.new(Discourse.system_user)
|
||||||
Chat::ChatReviewQueue.new.flag_message(
|
Chat::ChatReviewQueue.new.flag_message(message_1, guardian, ReviewableScore.types[:off_topic])
|
||||||
message_1,
|
|
||||||
guardian,
|
|
||||||
ReviewableScore.types[:off_topic],
|
|
||||||
)
|
|
||||||
|
|
||||||
reviewable = ReviewableChatMessage.last
|
reviewable = ReviewableChatMessage.last
|
||||||
expect(reviewable).to be_present
|
expect(reviewable).to be_present
|
||||||
|
|
|
@ -6,8 +6,8 @@ RSpec.shared_examples "a chatable model" do
|
||||||
|
|
||||||
it "returns a new chat channel model" do
|
it "returns a new chat channel model" do
|
||||||
expect(chat_channel).to have_attributes persisted?: false,
|
expect(chat_channel).to have_attributes persisted?: false,
|
||||||
class: channel_class,
|
class: channel_class,
|
||||||
chatable: chatable
|
chatable: chatable
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,9 @@ describe "Using #hashtag autocompletion to search for and lookup channels",
|
||||||
count: 3,
|
count: 3,
|
||||||
)
|
)
|
||||||
hashtag_results = page.all(".hashtag-autocomplete__link", count: 3)
|
hashtag_results = page.all(".hashtag-autocomplete__link", count: 3)
|
||||||
expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq(["Random", "Raspberry", "razed (x0)"])
|
expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq(
|
||||||
|
["Random", "Raspberry", "razed (x0)"],
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "searches for channels as well with # in a topic composer and deprioritises them" do
|
it "searches for channels as well with # in a topic composer and deprioritises them" do
|
||||||
|
@ -44,18 +46,26 @@ describe "Using #hashtag autocompletion to search for and lookup channels",
|
||||||
count: 3,
|
count: 3,
|
||||||
)
|
)
|
||||||
hashtag_results = page.all(".hashtag-autocomplete__link", count: 3)
|
hashtag_results = page.all(".hashtag-autocomplete__link", count: 3)
|
||||||
expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq(["Raspberry", "razed (x0)", "Random"])
|
expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq(
|
||||||
|
["Raspberry", "razed (x0)", "Random"],
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "cooks the hashtags for channels, categories, and tags serverside when the chat message is saved to the database" do
|
it "cooks the hashtags for channels, categories, and tags serverside when the chat message is saved to the database" do
|
||||||
chat_page.visit_channel(channel1)
|
chat_page.visit_channel(channel1)
|
||||||
expect(chat_channel_page).to have_no_loading_skeleton
|
expect(chat_channel_page).to have_no_loading_skeleton
|
||||||
chat_channel_page.type_in_composer("this is #random and this is #raspberry-beret and this is #razed which is cool")
|
chat_channel_page.type_in_composer(
|
||||||
|
"this is #random and this is #raspberry-beret and this is #razed which is cool",
|
||||||
|
)
|
||||||
chat_channel_page.click_send_message
|
chat_channel_page.click_send_message
|
||||||
|
|
||||||
message = nil
|
message = nil
|
||||||
try_until_success do
|
try_until_success do
|
||||||
message = ChatMessage.find_by(user: user, message: "this is #random and this is #raspberry-beret and this is #razed which is cool")
|
message =
|
||||||
|
ChatMessage.find_by(
|
||||||
|
user: user,
|
||||||
|
message: "this is #random and this is #raspberry-beret and this is #razed which is cool",
|
||||||
|
)
|
||||||
expect(message).not_to eq(nil)
|
expect(message).not_to eq(nil)
|
||||||
end
|
end
|
||||||
expect(chat_channel_page).to have_message(id: message.id)
|
expect(chat_channel_page).to have_message(id: message.id)
|
||||||
|
|
|
@ -43,8 +43,12 @@ RSpec.describe "List channels | mobile", type: :system, js: true, mobile: true d
|
||||||
it "sorts them alphabetically" do
|
it "sorts them alphabetically" do
|
||||||
visit("/chat")
|
visit("/chat")
|
||||||
|
|
||||||
expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq(channel_2.id.to_s)
|
expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq(
|
||||||
expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq(channel_1.id.to_s)
|
channel_2.id.to_s,
|
||||||
|
)
|
||||||
|
expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq(
|
||||||
|
channel_1.id.to_s,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,12 @@ RSpec.describe "List channels | no sidebar", type: :system, js: true do
|
||||||
it "sorts them alphabetically" do
|
it "sorts them alphabetically" do
|
||||||
visit("/chat")
|
visit("/chat")
|
||||||
|
|
||||||
expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq(channel_2.id.to_s)
|
expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq(
|
||||||
expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq(channel_1.id.to_s)
|
channel_2.id.to_s,
|
||||||
|
)
|
||||||
|
expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq(
|
||||||
|
channel_1.id.to_s,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,12 @@ RSpec.describe "List channels | sidebar", type: :system, js: true do
|
||||||
it "sorts them alphabetically" do
|
it "sorts them alphabetically" do
|
||||||
visit("/")
|
visit("/")
|
||||||
|
|
||||||
expect(page.find("#sidebar-section-content-chat-channels li:nth-child(1)")).to have_css(".channel-#{channel_2.id}")
|
expect(page.find("#sidebar-section-content-chat-channels li:nth-child(1)")).to have_css(
|
||||||
expect(page.find("#sidebar-section-content-chat-channels li:nth-child(2)")).to have_css(".channel-#{channel_1.id}")
|
".channel-#{channel_2.id}",
|
||||||
|
)
|
||||||
|
expect(page.find("#sidebar-section-content-chat-channels li:nth-child(2)")).to have_css(
|
||||||
|
".channel-#{channel_1.id}",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -43,14 +43,20 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
|
|
||||||
context "when clicking a link to a message from the current channel" do
|
context "when clicking a link to a message from the current channel" do
|
||||||
before do
|
before do
|
||||||
Fabricate(:chat_message, chat_channel: channel_1, message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})")
|
Fabricate(
|
||||||
|
:chat_message,
|
||||||
|
chat_channel: channel_1,
|
||||||
|
message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "highglights the correct message" do
|
it "highglights the correct message" do
|
||||||
chat_page.visit_channel(channel_1)
|
chat_page.visit_channel(channel_1)
|
||||||
click_link(link)
|
click_link(link)
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']")
|
expect(page).to have_css(
|
||||||
|
".chat-message-container.highlighted[data-id='#{first_message.id}']",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "highlights the correct message after using the bottom arrow" do
|
it "highlights the correct message after using the bottom arrow" do
|
||||||
|
@ -59,7 +65,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
click_link(I18n.t("js.chat.scroll_to_bottom"))
|
click_link(I18n.t("js.chat.scroll_to_bottom"))
|
||||||
click_link(link)
|
click_link(link)
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']")
|
expect(page).to have_css(
|
||||||
|
".chat-message-container.highlighted[data-id='#{first_message.id}']",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,7 +75,11 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
fab!(:channel_2) { Fabricate(:category_channel) }
|
fab!(:channel_2) { Fabricate(:category_channel) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Fabricate(:chat_message, chat_channel: channel_2, message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})")
|
Fabricate(
|
||||||
|
:chat_message,
|
||||||
|
chat_channel: channel_2,
|
||||||
|
message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})",
|
||||||
|
)
|
||||||
channel_2.add(current_user)
|
channel_2.add(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,7 +87,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
chat_page.visit_channel(channel_2)
|
chat_page.visit_channel(channel_2)
|
||||||
click_link(link)
|
click_link(link)
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']")
|
expect(page).to have_css(
|
||||||
|
".chat-message-container.highlighted[data-id='#{first_message.id}']",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -83,7 +97,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
it "highglights the correct message" do
|
it "highglights the correct message" do
|
||||||
visit("/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id}")
|
visit("/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id}")
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']")
|
expect(page).to have_css(
|
||||||
|
".chat-message-container.highlighted[data-id='#{first_message.id}']",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -113,7 +129,11 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
|
|
||||||
context "when clicking a link to a message from the current channel" do
|
context "when clicking a link to a message from the current channel" do
|
||||||
before do
|
before do
|
||||||
Fabricate(:chat_message, chat_channel: channel_1, message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})")
|
Fabricate(
|
||||||
|
:chat_message,
|
||||||
|
chat_channel: channel_1,
|
||||||
|
message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "highglights the correct message" do
|
it "highglights the correct message" do
|
||||||
|
@ -122,7 +142,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
chat_drawer_page.open_channel(channel_1)
|
chat_drawer_page.open_channel(channel_1)
|
||||||
click_link(link)
|
click_link(link)
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']")
|
expect(page).to have_css(
|
||||||
|
".chat-message-container.highlighted[data-id='#{first_message.id}']",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "highlights the correct message after using the bottom arrow" do
|
it "highlights the correct message after using the bottom arrow" do
|
||||||
|
@ -133,7 +155,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do
|
||||||
click_link(I18n.t("js.chat.scroll_to_bottom"))
|
click_link(I18n.t("js.chat.scroll_to_bottom"))
|
||||||
click_link(link)
|
click_link(link)
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']")
|
expect(page).to have_css(
|
||||||
|
".chat-message-container.highlighted[data-id='#{first_message.id}']",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,9 @@ module PageObjects
|
||||||
module Pages
|
module Pages
|
||||||
class Chat < PageObjects::Pages::Base
|
class Chat < PageObjects::Pages::Base
|
||||||
def prefers_full_page
|
def prefers_full_page
|
||||||
page.execute_script("window.localStorage.setItem('discourse_chat_preferred_mode', '\"FULL_PAGE_CHAT\"');")
|
page.execute_script(
|
||||||
|
"window.localStorage.setItem('discourse_chat_preferred_mode', '\"FULL_PAGE_CHAT\"');",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def open_from_header
|
def open_from_header
|
||||||
|
|
|
@ -12,32 +12,34 @@ hide_plugin if self.respond_to?(:hide_plugin)
|
||||||
register_asset "stylesheets/details.scss"
|
register_asset "stylesheets/details.scss"
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
|
||||||
Email::Styles.register_plugin_style do |fragment|
|
Email::Styles.register_plugin_style do |fragment|
|
||||||
# remove all elided content
|
# remove all elided content
|
||||||
fragment.css("details.elided").each(&:remove)
|
fragment.css("details.elided").each(&:remove)
|
||||||
|
|
||||||
# replace all details with their summary in emails
|
# replace all details with their summary in emails
|
||||||
fragment.css("details").each do |details|
|
fragment
|
||||||
summary = details.css("summary")
|
.css("details")
|
||||||
if summary && summary[0]
|
.each do |details|
|
||||||
summary = summary[0]
|
summary = details.css("summary")
|
||||||
if summary && summary.respond_to?(:name)
|
if summary && summary[0]
|
||||||
summary.name = "p"
|
summary = summary[0]
|
||||||
details.replace(summary)
|
if summary && summary.respond_to?(:name)
|
||||||
|
summary.name = "p"
|
||||||
|
details.replace(summary)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:reduce_cooked) do |fragment, post|
|
on(:reduce_cooked) do |fragment, post|
|
||||||
fragment.css("details").each do |el|
|
fragment
|
||||||
text = el.css("summary").text
|
.css("details")
|
||||||
link = fragment.document.create_element("a")
|
.each do |el|
|
||||||
link["href"] = post.url if post
|
text = el.css("summary").text
|
||||||
link.content = I18n.t("details.excerpt_details")
|
link = fragment.document.create_element("a")
|
||||||
el.replace CGI.escapeHTML(text) + " " + link.to_html
|
link["href"] = post.url if post
|
||||||
end
|
link.content = I18n.t("details.excerpt_details")
|
||||||
|
el.replace CGI.escapeHTML(text) + " " + link.to_html
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rails_helper'
|
require "rails_helper"
|
||||||
require 'pretty_text'
|
require "pretty_text"
|
||||||
|
|
||||||
RSpec.describe PrettyText do
|
RSpec.describe PrettyText do
|
||||||
|
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
|
|
||||||
it "supports details tag" do
|
it "supports details tag" do
|
||||||
|
@ -17,17 +16,19 @@ RSpec.describe PrettyText do
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
expect(cooked_html).to match_html(cooked_html)
|
expect(cooked_html).to match_html(cooked_html)
|
||||||
expect(PrettyText.cook("[details=foo]\nbar\n[/details]").gsub("\n", "")).to match_html(cooked_html)
|
expect(PrettyText.cook("[details=foo]\nbar\n[/details]").gsub("\n", "")).to match_html(
|
||||||
|
cooked_html,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "deletes elided content" do
|
it "deletes elided content" do
|
||||||
cooked_html = PrettyText.cook("Hello World\n\n<details class='elided'>42</details>")
|
cooked_html = PrettyText.cook("Hello World\n\n<details class='elided'>42</details>")
|
||||||
mail_html = "<p>Hello World</p>\n<a href=\"http://test.localhost\">(click for more details)</a>"
|
mail_html = "<p>Hello World</p>\n<a href=\"http://test.localhost\">(click for more details)</a>"
|
||||||
|
|
||||||
expect(PrettyText.format_for_email(cooked_html)).to match_html(mail_html)
|
expect(PrettyText.format_for_email(cooked_html)).to match_html(mail_html)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'can replace spoilers in emails' do
|
it "can replace spoilers in emails" do
|
||||||
md = PrettyText.cook(<<~MD)
|
md = PrettyText.cook(<<~MD)
|
||||||
hello
|
hello
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ RSpec.describe PrettyText do
|
||||||
expect(md).to eq(html)
|
expect(md).to eq(html)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'properly handles multiple spoiler blocks in a post' do
|
it "properly handles multiple spoiler blocks in a post" do
|
||||||
md = PrettyText.cook(<<~MD)
|
md = PrettyText.cook(<<~MD)
|
||||||
[details="First"]
|
[details="First"]
|
||||||
body secret stuff very long
|
body secret stuff very long
|
||||||
|
@ -58,13 +59,13 @@ RSpec.describe PrettyText do
|
||||||
MD
|
MD
|
||||||
|
|
||||||
md = PrettyText.format_for_email(md, post)
|
md = PrettyText.format_for_email(md, post)
|
||||||
expect(md).not_to include('secret stuff')
|
expect(md).not_to include("secret stuff")
|
||||||
expect(md.scan(/First/).size).to eq(1)
|
expect(md.scan(/First/).size).to eq(1)
|
||||||
expect(md.scan(/Third/).size).to eq(1)
|
expect(md.scan(/Third/).size).to eq(1)
|
||||||
expect(md.scan(I18n.t('details.excerpt_details')).size).to eq(3)
|
expect(md.scan(I18n.t("details.excerpt_details")).size).to eq(3)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'escapes summary text' do
|
it "escapes summary text" do
|
||||||
md = PrettyText.cook(<<~MD)
|
md = PrettyText.cook(<<~MD)
|
||||||
<script>alert('hello')</script>
|
<script>alert('hello')</script>
|
||||||
[details="<script>alert('hello')</script>"]
|
[details="<script>alert('hello')</script>"]
|
||||||
|
@ -73,7 +74,6 @@ RSpec.describe PrettyText do
|
||||||
MD
|
MD
|
||||||
md = PrettyText.format_for_email(md, post)
|
md = PrettyText.format_for_email(md, post)
|
||||||
|
|
||||||
expect(md).not_to include('<script>')
|
expect(md).not_to include("<script>")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,40 +7,40 @@
|
||||||
|
|
||||||
hide_plugin if self.respond_to?(:hide_plugin)
|
hide_plugin if self.respond_to?(:hide_plugin)
|
||||||
|
|
||||||
register_asset 'stylesheets/common/discourse-local-dates.scss'
|
register_asset "stylesheets/common/discourse-local-dates.scss"
|
||||||
register_asset 'moment.js', :vendored_core_pretty_text
|
register_asset "moment.js", :vendored_core_pretty_text
|
||||||
register_asset 'moment-timezone.js', :vendored_core_pretty_text
|
register_asset "moment-timezone.js", :vendored_core_pretty_text
|
||||||
|
|
||||||
enabled_site_setting :discourse_local_dates_enabled
|
enabled_site_setting :discourse_local_dates_enabled
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
module ::DiscourseLocalDates
|
module ::DiscourseLocalDates
|
||||||
PLUGIN_NAME ||= 'discourse-local-dates'.freeze
|
PLUGIN_NAME ||= "discourse-local-dates".freeze
|
||||||
POST_CUSTOM_FIELD ||= 'local_dates'.freeze
|
POST_CUSTOM_FIELD ||= "local_dates".freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
%w[../lib/discourse_local_dates/engine.rb].each do |path|
|
%w[../lib/discourse_local_dates/engine.rb].each { |path| load File.expand_path(path, __FILE__) }
|
||||||
load File.expand_path(path, __FILE__)
|
|
||||||
end
|
|
||||||
|
|
||||||
register_post_custom_field_type(DiscourseLocalDates::POST_CUSTOM_FIELD, :json)
|
register_post_custom_field_type(DiscourseLocalDates::POST_CUSTOM_FIELD, :json)
|
||||||
|
|
||||||
on(:before_post_process_cooked) do |doc, post|
|
on(:before_post_process_cooked) do |doc, post|
|
||||||
dates = []
|
dates = []
|
||||||
|
|
||||||
doc.css('span.discourse-local-date').map do |cooked_date|
|
doc
|
||||||
next if cooked_date.ancestors("aside").length > 0
|
.css("span.discourse-local-date")
|
||||||
date = {}
|
.map do |cooked_date|
|
||||||
cooked_date.attributes.values.each do |attribute|
|
next if cooked_date.ancestors("aside").length > 0
|
||||||
data_name = attribute.name&.gsub('data-', '')
|
date = {}
|
||||||
if data_name && %w[date time timezone recurring].include?(data_name)
|
cooked_date.attributes.values.each do |attribute|
|
||||||
unless attribute.value == 'undefined'
|
data_name = attribute.name&.gsub("data-", "")
|
||||||
date[data_name] = CGI.escapeHTML(attribute.value || '')
|
if data_name && %w[date time timezone recurring].include?(data_name)
|
||||||
|
unless attribute.value == "undefined"
|
||||||
|
date[data_name] = CGI.escapeHTML(attribute.value || "")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
dates << date
|
||||||
end
|
end
|
||||||
dates << date
|
|
||||||
end
|
|
||||||
|
|
||||||
if dates.present?
|
if dates.present?
|
||||||
post.custom_fields[DiscourseLocalDates::POST_CUSTOM_FIELD] = dates
|
post.custom_fields[DiscourseLocalDates::POST_CUSTOM_FIELD] = dates
|
||||||
|
@ -51,22 +51,22 @@ after_initialize do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_class(:post, :local_dates) do
|
add_to_class(:post, :local_dates) { custom_fields[DiscourseLocalDates::POST_CUSTOM_FIELD] || [] }
|
||||||
custom_fields[DiscourseLocalDates::POST_CUSTOM_FIELD] || []
|
|
||||||
end
|
|
||||||
|
|
||||||
on(:reduce_excerpt) do |fragment, post|
|
on(:reduce_excerpt) do |fragment, post|
|
||||||
fragment.css('.discourse-local-date').each do |container|
|
fragment
|
||||||
container.content = "#{container.content} (UTC)"
|
.css(".discourse-local-date")
|
||||||
end
|
.each { |container| container.content = "#{container.content} (UTC)" }
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:reduce_cooked) do |fragment|
|
on(:reduce_cooked) do |fragment|
|
||||||
fragment.css('.discourse-local-date').each do |container|
|
fragment
|
||||||
if container.attributes['data-email-preview']
|
.css(".discourse-local-date")
|
||||||
preview = container.attributes['data-email-preview'].value
|
.each do |container|
|
||||||
container.content = preview
|
if container.attributes["data-email-preview"]
|
||||||
|
preview = container.attributes["data-email-preview"].value
|
||||||
|
container.content = preview
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe "Local Dates" do
|
RSpec.describe "Local Dates" do
|
||||||
before do
|
before { freeze_time DateTime.parse("2018-11-10 12:00") }
|
||||||
freeze_time DateTime.parse('2018-11-10 12:00')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should work without timezone" do
|
it "should work without timezone" do
|
||||||
post = Fabricate(:post, raw: <<~MD)
|
post = Fabricate(:post, raw: <<~MD)
|
||||||
|
@ -15,14 +13,12 @@ RSpec.describe "Local Dates" do
|
||||||
expect(cooked).to include('class="discourse-local-date"')
|
expect(cooked).to include('class="discourse-local-date"')
|
||||||
expect(cooked).to include('data-date="2018-05-08"')
|
expect(cooked).to include('data-date="2018-05-08"')
|
||||||
expect(cooked).to include('data-format="L LTS"')
|
expect(cooked).to include('data-format="L LTS"')
|
||||||
expect(cooked).not_to include('data-timezone=')
|
expect(cooked).not_to include("data-timezone=")
|
||||||
|
|
||||||
expect(cooked).to include(
|
expect(cooked).to include('data-timezones="Europe/Paris|America/Los_Angeles"')
|
||||||
'data-timezones="Europe/Paris|America/Los_Angeles"'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(cooked).to include('data-email-preview="2018-05-08T22:00:00Z UTC"')
|
expect(cooked).to include('data-email-preview="2018-05-08T22:00:00Z UTC"')
|
||||||
expect(cooked).to include('05/08/2018 10:00:00 PM')
|
expect(cooked).to include("05/08/2018 10:00:00 PM")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should work with timezone" do
|
it "should work with timezone" do
|
||||||
|
@ -33,10 +29,10 @@ RSpec.describe "Local Dates" do
|
||||||
cooked = post.cooked
|
cooked = post.cooked
|
||||||
|
|
||||||
expect(cooked).to include('data-timezone="Asia/Calcutta"')
|
expect(cooked).to include('data-timezone="Asia/Calcutta"')
|
||||||
expect(cooked).to include('05/08/2018 4:30:00 PM')
|
expect(cooked).to include("05/08/2018 4:30:00 PM")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'requires the right attributes to convert to a local date' do
|
it "requires the right attributes to convert to a local date" do
|
||||||
post = Fabricate(:post, raw: <<~MD)
|
post = Fabricate(:post, raw: <<~MD)
|
||||||
[date]
|
[date]
|
||||||
MD
|
MD
|
||||||
|
@ -44,10 +40,10 @@ RSpec.describe "Local Dates" do
|
||||||
cooked = post.cooked
|
cooked = post.cooked
|
||||||
|
|
||||||
expect(post.cooked).to include("<p>[date]</p>")
|
expect(post.cooked).to include("<p>[date]</p>")
|
||||||
expect(cooked).to_not include('data-date=')
|
expect(cooked).to_not include("data-date=")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'requires the right attributes to convert to a local date' do
|
it "requires the right attributes to convert to a local date" do
|
||||||
post = Fabricate(:post, raw: <<~MD)
|
post = Fabricate(:post, raw: <<~MD)
|
||||||
[date]
|
[date]
|
||||||
MD
|
MD
|
||||||
|
@ -55,48 +51,48 @@ RSpec.describe "Local Dates" do
|
||||||
cooked = post.cooked
|
cooked = post.cooked
|
||||||
|
|
||||||
expect(post.cooked).to include("<p>[date]</p>")
|
expect(post.cooked).to include("<p>[date]</p>")
|
||||||
expect(cooked).to_not include('data-date=')
|
expect(cooked).to_not include("data-date=")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'it works with only a date and time' do
|
it "it works with only a date and time" do
|
||||||
raw = "[date=2018-11-01 time=12:00]"
|
raw = "[date=2018-11-01 time=12:00]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
expect(cooked).to include('data-date="2018-11-01"')
|
expect(cooked).to include('data-date="2018-11-01"')
|
||||||
expect(cooked).to include('data-time="12:00"')
|
expect(cooked).to include('data-time="12:00"')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'doesn’t include format by default' do
|
it "doesn’t include format by default" do
|
||||||
raw = "[date=2018-11-01 time=12:00]"
|
raw = "[date=2018-11-01 time=12:00]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
expect(cooked).not_to include('data-format=')
|
expect(cooked).not_to include("data-format=")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'doesn’t include timezone by default' do
|
it "doesn’t include timezone by default" do
|
||||||
raw = "[date=2018-11-01 time=12:00]"
|
raw = "[date=2018-11-01 time=12:00]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
expect(cooked).not_to include("data-timezone=")
|
expect(cooked).not_to include("data-timezone=")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'supports countdowns' do
|
it "supports countdowns" do
|
||||||
raw = "[date=2018-11-01 time=12:00 countdown=true]"
|
raw = "[date=2018-11-01 time=12:00 countdown=true]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
expect(cooked).to include("data-countdown=")
|
expect(cooked).to include("data-countdown=")
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'ranges' do
|
describe "ranges" do
|
||||||
it 'generates ranges without time' do
|
it "generates ranges without time" do
|
||||||
raw = "[date-range from=2022-01-06 to=2022-01-08]"
|
raw = "[date-range from=2022-01-06 to=2022-01-08]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
expect(cooked).to include('data-date="2022-01-06')
|
expect(cooked).to include('data-date="2022-01-06')
|
||||||
expect(cooked).to include('data-range="from"')
|
expect(cooked).to include('data-range="from"')
|
||||||
expect(cooked).to include('data-range="to"')
|
expect(cooked).to include('data-range="to"')
|
||||||
expect(cooked).not_to include('data-time=')
|
expect(cooked).not_to include("data-time=")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'supports time and timezone' do
|
it "supports time and timezone" do
|
||||||
raw = "[date-range from=2022-01-06T13:00 to=2022-01-08 timezone=Australia/Sydney]"
|
raw = "[date-range from=2022-01-06T13:00 to=2022-01-08 timezone=Australia/Sydney]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
|
@ -107,7 +103,7 @@ RSpec.describe "Local Dates" do
|
||||||
expect(cooked).to include('data-timezone="Australia/Sydney"')
|
expect(cooked).to include('data-timezone="Australia/Sydney"')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'generates single date when range without end date' do
|
it "generates single date when range without end date" do
|
||||||
raw = "[date-range from=2022-01-06T13:00]"
|
raw = "[date-range from=2022-01-06T13:00]"
|
||||||
cooked = Fabricate(:post, raw: raw).cooked
|
cooked = Fabricate(:post, raw: raw).cooked
|
||||||
|
|
||||||
|
|
|
@ -15,94 +15,118 @@ def generate_html(text, opts = {})
|
||||||
end
|
end
|
||||||
|
|
||||||
RSpec.describe PrettyText do
|
RSpec.describe PrettyText do
|
||||||
before do
|
before { freeze_time }
|
||||||
freeze_time
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'emails simplified rendering' do
|
describe "emails simplified rendering" do
|
||||||
it 'works with default markup' do
|
it "works with default markup" do
|
||||||
cooked = PrettyText.cook("[date=2018-05-08]")
|
cooked = PrettyText.cook("[date=2018-05-08]")
|
||||||
cooked_mail = generate_html("2018-05-08T00:00:00Z UTC",
|
cooked_mail =
|
||||||
date: "2018-05-08",
|
generate_html(
|
||||||
email_preview: "2018-05-08T00:00:00Z UTC"
|
"2018-05-08T00:00:00Z UTC",
|
||||||
)
|
|
||||||
|
|
||||||
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'works with time' do
|
|
||||||
cooked = PrettyText.cook("[date=2018-05-08 time=20:00:00]")
|
|
||||||
cooked_mail = generate_html("2018-05-08T20:00:00Z UTC",
|
|
||||||
date: "2018-05-08",
|
|
||||||
email_preview: "2018-05-08T20:00:00Z UTC",
|
|
||||||
time: "20:00:00"
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'works with multiple timezones' do
|
|
||||||
cooked = PrettyText.cook('[date=2018-05-08 timezone="Europe/Paris" timezones="America/Los_Angeles|Pacific/Auckland"]')
|
|
||||||
cooked_mail = generate_html("2018-05-07T22:00:00Z UTC",
|
|
||||||
date: "2018-05-08",
|
|
||||||
email_preview: "2018-05-07T22:00:00Z UTC",
|
|
||||||
timezone: "Europe/Paris",
|
|
||||||
timezones: "America/Los_Angeles|Pacific/Auckland"
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'discourse_local_dates_email_format' do
|
|
||||||
before do
|
|
||||||
SiteSetting.discourse_local_dates_email_format = "DD/MM"
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'uses the site setting' do
|
|
||||||
cooked = PrettyText.cook("[date=2018-05-08]")
|
|
||||||
cooked_mail = generate_html("08/05 UTC",
|
|
||||||
date: "2018-05-08",
|
date: "2018-05-08",
|
||||||
email_preview: "08/05 UTC"
|
email_preview: "2018-05-08T00:00:00Z UTC",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works with time" do
|
||||||
|
cooked = PrettyText.cook("[date=2018-05-08 time=20:00:00]")
|
||||||
|
cooked_mail =
|
||||||
|
generate_html(
|
||||||
|
"2018-05-08T20:00:00Z UTC",
|
||||||
|
date: "2018-05-08",
|
||||||
|
email_preview: "2018-05-08T20:00:00Z UTC",
|
||||||
|
time: "20:00:00",
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works with multiple timezones" do
|
||||||
|
cooked =
|
||||||
|
PrettyText.cook(
|
||||||
|
'[date=2018-05-08 timezone="Europe/Paris" timezones="America/Los_Angeles|Pacific/Auckland"]',
|
||||||
|
)
|
||||||
|
cooked_mail =
|
||||||
|
generate_html(
|
||||||
|
"2018-05-07T22:00:00Z UTC",
|
||||||
|
date: "2018-05-08",
|
||||||
|
email_preview: "2018-05-07T22:00:00Z UTC",
|
||||||
|
timezone: "Europe/Paris",
|
||||||
|
timezones: "America/Los_Angeles|Pacific/Auckland",
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "discourse_local_dates_email_format" do
|
||||||
|
before { SiteSetting.discourse_local_dates_email_format = "DD/MM" }
|
||||||
|
|
||||||
|
it "uses the site setting" do
|
||||||
|
cooked = PrettyText.cook("[date=2018-05-08]")
|
||||||
|
cooked_mail = generate_html("08/05 UTC", date: "2018-05-08", email_preview: "08/05 UTC")
|
||||||
|
|
||||||
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
expect(PrettyText.format_for_email(cooked)).to match_html(cooked_mail)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'excerpt simplified rendering' do
|
describe "excerpt simplified rendering" do
|
||||||
let(:post) { Fabricate(:post, raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone="America/New_York"]') }
|
let(:post) do
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone="America/New_York"]',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it 'adds UTC' do
|
it "adds UTC" do
|
||||||
excerpt = PrettyText.excerpt(post.cooked, 200)
|
excerpt = PrettyText.excerpt(post.cooked, 200)
|
||||||
expect(excerpt).to eq("Wednesday, October 16, 2019 6:00 PM (UTC)")
|
expect(excerpt).to eq("Wednesday, October 16, 2019 6:00 PM (UTC)")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'special quotes' do
|
describe "special quotes" do
|
||||||
it 'converts special quotes to regular quotes' do
|
it "converts special quotes to regular quotes" do
|
||||||
# german
|
# german
|
||||||
post = Fabricate(:post, raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=„America/New_York“]')
|
post =
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=„America/New_York“]',
|
||||||
|
)
|
||||||
excerpt = PrettyText.excerpt(post.cooked, 200)
|
excerpt = PrettyText.excerpt(post.cooked, 200)
|
||||||
expect(excerpt).to eq('Wednesday, October 16, 2019 6:00 PM (UTC)')
|
expect(excerpt).to eq("Wednesday, October 16, 2019 6:00 PM (UTC)")
|
||||||
|
|
||||||
# french
|
# french
|
||||||
post = Fabricate(:post, raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=«America/New_York»]')
|
post =
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=«America/New_York»]',
|
||||||
|
)
|
||||||
excerpt = PrettyText.excerpt(post.cooked, 200)
|
excerpt = PrettyText.excerpt(post.cooked, 200)
|
||||||
expect(excerpt).to eq('Wednesday, October 16, 2019 6:00 PM (UTC)')
|
expect(excerpt).to eq("Wednesday, October 16, 2019 6:00 PM (UTC)")
|
||||||
|
|
||||||
post = Fabricate(:post, raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=“America/New_York”]')
|
post =
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=“America/New_York”]',
|
||||||
|
)
|
||||||
excerpt = PrettyText.excerpt(post.cooked, 200)
|
excerpt = PrettyText.excerpt(post.cooked, 200)
|
||||||
expect(excerpt).to eq('Wednesday, October 16, 2019 6:00 PM (UTC)')
|
expect(excerpt).to eq("Wednesday, October 16, 2019 6:00 PM (UTC)")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'french quotes' do
|
describe "french quotes" do
|
||||||
let(:post) { Fabricate(:post, raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=«America/New_York»]') }
|
let(:post) do
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
raw: '[date=2019-10-16 time=14:00:00 format="LLLL" timezone=«America/New_York»]',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it 'converts french quotes to regular quotes' do
|
it "converts french quotes to regular quotes" do
|
||||||
excerpt = PrettyText.excerpt(post.cooked, 200)
|
excerpt = PrettyText.excerpt(post.cooked, 200)
|
||||||
expect(excerpt).to eq('Wednesday, October 16, 2019 6:00 PM (UTC)')
|
expect(excerpt).to eq("Wednesday, October 16, 2019 6:00 PM (UTC)")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe Post do
|
RSpec.describe Post do
|
||||||
|
before { Jobs.run_immediately! }
|
||||||
|
|
||||||
before do
|
describe "#local_dates" do
|
||||||
Jobs.run_immediately!
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#local_dates' do
|
|
||||||
it "should have correct custom fields" do
|
it "should have correct custom fields" do
|
||||||
post = Fabricate(:post, raw: <<~SQL)
|
post = Fabricate(:post, raw: <<~SQL)
|
||||||
[date=2018-09-17 time=01:39:00 format="LLL" timezone="Europe/Paris" timezones="Europe/Paris|America/Los_Angeles"]
|
[date=2018-09-17 time=01:39:00 format="LLL" timezone="Europe/Paris" timezones="Europe/Paris|America/Los_Angeles"]
|
||||||
|
@ -37,7 +34,7 @@ RSpec.describe Post do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not contain dates from examples" do
|
it "should not contain dates from examples" do
|
||||||
Oneboxer.stubs(:cached_onebox).with('https://example.com').returns(<<-HTML)
|
Oneboxer.stubs(:cached_onebox).with("https://example.com").returns(<<-HTML)
|
||||||
<aside class="onebox githubcommit">
|
<aside class="onebox githubcommit">
|
||||||
<span class="discourse-local-date" data-format="ll" data-date="2020-01-20" data-time="15:06:58" data-timezone="UTC">03:06PM - 20 Jan 20 UTC</span>
|
<span class="discourse-local-date" data-format="ll" data-date="2020-01-20" data-time="15:06:58" data-timezone="UTC">03:06PM - 20 Jan 20 UTC</span>
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -48,5 +45,4 @@ RSpec.describe Post do
|
||||||
expect(post.local_dates.count).to eq(0)
|
expect(post.local_dates.count).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,18 +4,11 @@ describe "Local dates", type: :system, js: true do
|
||||||
fab!(:topic) { Fabricate(:topic) }
|
fab!(:topic) { Fabricate(:topic) }
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
before do
|
before { create_post(user: user, topic: topic, title: "Date range test post", raw: <<~RAW) }
|
||||||
create_post(
|
|
||||||
user: user,
|
|
||||||
topic: topic,
|
|
||||||
title: "Date range test post",
|
|
||||||
raw: <<~RAW
|
|
||||||
First option: [date=2022-12-15 time=14:19:00 timezone="Asia/Singapore"]
|
First option: [date=2022-12-15 time=14:19:00 timezone="Asia/Singapore"]
|
||||||
Second option: [date=2022-12-15 time=01:20:00 timezone="Asia/Singapore"], or [date=2022-12-15 time=02:40:00 timezone="Asia/Singapore"]
|
Second option: [date=2022-12-15 time=01:20:00 timezone="Asia/Singapore"], or [date=2022-12-15 time=02:40:00 timezone="Asia/Singapore"]
|
||||||
Third option: [date-range from=2022-12-15T11:25:00 to=2022-12-16T00:26:00 timezone="Asia/Singapore"] or [date-range from=2022-12-22T11:57:00 to=2022-12-23T11:58:00 timezone="Asia/Singapore"]
|
Third option: [date-range from=2022-12-15T11:25:00 to=2022-12-16T00:26:00 timezone="Asia/Singapore"] or [date-range from=2022-12-22T11:57:00 to=2022-12-23T11:58:00 timezone="Asia/Singapore"]
|
||||||
RAW
|
RAW
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||||
|
|
||||||
|
@ -53,12 +46,18 @@ describe "Local dates", type: :system, js: true do
|
||||||
post_dates[3].click
|
post_dates[3].click
|
||||||
tippy_date = topic_page.find(".tippy-content .current .date-time")
|
tippy_date = topic_page.find(".tippy-content .current .date-time")
|
||||||
|
|
||||||
expect(tippy_date).to have_text("Thursday, December 15, 2022\n11:25 AM → 12:26 AM", exact: true)
|
expect(tippy_date).to have_text(
|
||||||
|
"Thursday, December 15, 2022\n11:25 AM → 12:26 AM",
|
||||||
|
exact: true,
|
||||||
|
)
|
||||||
|
|
||||||
post_dates[5].click
|
post_dates[5].click
|
||||||
tippy_date = topic_page.find(".tippy-content .current .date-time")
|
tippy_date = topic_page.find(".tippy-content .current .date-time")
|
||||||
|
|
||||||
expect(tippy_date).to have_text("Thursday, December 22, 2022 11:57 AM → Friday, December 23, 2022 11:58 AM", exact: true)
|
expect(tippy_date).to have_text(
|
||||||
|
"Thursday, December 22, 2022 11:57 AM → Friday, December 23, 2022 11:58 AM",
|
||||||
|
exact: true,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,33 +4,29 @@ module Jobs
|
||||||
module DiscourseNarrativeBot
|
module DiscourseNarrativeBot
|
||||||
class GrantBadges < ::Jobs::Onceoff
|
class GrantBadges < ::Jobs::Onceoff
|
||||||
def execute_onceoff(args)
|
def execute_onceoff(args)
|
||||||
new_user_track_badge = Badge.find_by(
|
new_user_track_badge =
|
||||||
name: ::DiscourseNarrativeBot::NewUserNarrative.badge_name
|
Badge.find_by(name: ::DiscourseNarrativeBot::NewUserNarrative.badge_name)
|
||||||
)
|
|
||||||
|
|
||||||
advanced_user_track_badge = Badge.find_by(
|
advanced_user_track_badge =
|
||||||
name: ::DiscourseNarrativeBot::AdvancedUserNarrative.badge_name
|
Badge.find_by(name: ::DiscourseNarrativeBot::AdvancedUserNarrative.badge_name)
|
||||||
)
|
|
||||||
|
|
||||||
PluginStoreRow.where(
|
PluginStoreRow
|
||||||
plugin_name: ::DiscourseNarrativeBot::PLUGIN_NAME,
|
.where(plugin_name: ::DiscourseNarrativeBot::PLUGIN_NAME, type_name: "JSON")
|
||||||
type_name: 'JSON'
|
.find_each do |row|
|
||||||
).find_each do |row|
|
value = JSON.parse(row.value)
|
||||||
|
completed = value["completed"]
|
||||||
|
user = User.find_by(id: row.key)
|
||||||
|
|
||||||
value = JSON.parse(row.value)
|
if user && completed
|
||||||
completed = value["completed"]
|
if completed.include?(::DiscourseNarrativeBot::NewUserNarrative.to_s)
|
||||||
user = User.find_by(id: row.key)
|
BadgeGranter.grant(new_user_track_badge, user)
|
||||||
|
end
|
||||||
|
|
||||||
if user && completed
|
if completed.include?(::DiscourseNarrativeBot::AdvancedUserNarrative.to_s)
|
||||||
if completed.include?(::DiscourseNarrativeBot::NewUserNarrative.to_s)
|
BadgeGranter.grant(advanced_user_track_badge, user)
|
||||||
BadgeGranter.grant(new_user_track_badge, user)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if completed.include?(::DiscourseNarrativeBot::AdvancedUserNarrative.to_s)
|
|
||||||
BadgeGranter.grant(advanced_user_track_badge, user)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,34 +4,40 @@ module Jobs
|
||||||
module DiscourseNarrativeBot
|
module DiscourseNarrativeBot
|
||||||
class RemapOldBotImages < ::Jobs::Onceoff
|
class RemapOldBotImages < ::Jobs::Onceoff
|
||||||
def execute_onceoff(args)
|
def execute_onceoff(args)
|
||||||
paths = [
|
paths = %w[
|
||||||
"/images/font-awesome-link.png",
|
/images/font-awesome-link.png
|
||||||
"/images/unicorn.png",
|
/images/unicorn.png
|
||||||
"/images/font-awesome-ellipsis.png",
|
/images/font-awesome-ellipsis.png
|
||||||
"/images/font-awesome-bookmark.png",
|
/images/font-awesome-bookmark.png
|
||||||
"/images/font-awesome-smile.png",
|
/images/font-awesome-smile.png
|
||||||
"/images/font-awesome-flag.png",
|
/images/font-awesome-flag.png
|
||||||
"/images/font-awesome-search.png",
|
/images/font-awesome-search.png
|
||||||
"/images/capybara-eating.gif",
|
/images/capybara-eating.gif
|
||||||
"/images/font-awesome-pencil.png",
|
/images/font-awesome-pencil.png
|
||||||
"/images/font-awesome-trash.png",
|
/images/font-awesome-trash.png
|
||||||
"/images/font-awesome-rotate-left.png",
|
/images/font-awesome-rotate-left.png
|
||||||
"/images/font-awesome-gear.png",
|
/images/font-awesome-gear.png
|
||||||
]
|
]
|
||||||
|
|
||||||
Post.raw_match("/images/").where(user_id: -2).find_each do |post|
|
Post
|
||||||
if (matches = post.raw.scan(/(?<!\/plugins\/discourse-narrative-bot)(#{paths.join("|")})/)).present?
|
.raw_match("/images/")
|
||||||
new_raw = post.raw
|
.where(user_id: -2)
|
||||||
|
.find_each do |post|
|
||||||
|
if (
|
||||||
|
matches =
|
||||||
|
post.raw.scan(%r{(?<!/plugins/discourse-narrative-bot)(#{paths.join("|")})})
|
||||||
|
).present?
|
||||||
|
new_raw = post.raw
|
||||||
|
|
||||||
matches.each do |match|
|
matches.each do |match|
|
||||||
path = match.first
|
path = match.first
|
||||||
new_raw = new_raw.gsub(path, "/plugins/discourse-narrative-bot#{path}")
|
new_raw = new_raw.gsub(path, "/plugins/discourse-narrative-bot#{path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
post.update_columns(raw: new_raw)
|
||||||
|
post.rebake!
|
||||||
end
|
end
|
||||||
|
|
||||||
post.update_columns(raw: new_raw)
|
|
||||||
post.rebake!
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,16 +2,17 @@
|
||||||
|
|
||||||
module Jobs
|
module Jobs
|
||||||
class BotInput < ::Jobs::Base
|
class BotInput < ::Jobs::Base
|
||||||
|
sidekiq_options queue: "critical", retry: false
|
||||||
sidekiq_options queue: 'critical', retry: false
|
|
||||||
|
|
||||||
def execute(args)
|
def execute(args)
|
||||||
return unless user = User.find_by(id: args[:user_id])
|
return unless user = User.find_by(id: args[:user_id])
|
||||||
|
|
||||||
I18n.with_locale(user.effective_locale) do
|
I18n.with_locale(user.effective_locale) do
|
||||||
::DiscourseNarrativeBot::TrackSelector.new(args[:input].to_sym, user,
|
::DiscourseNarrativeBot::TrackSelector.new(
|
||||||
|
args[:input].to_sym,
|
||||||
|
user,
|
||||||
post_id: args[:post_id],
|
post_id: args[:post_id],
|
||||||
topic_id: args[:topic_id]
|
topic_id: args[:topic_id],
|
||||||
).select
|
).select
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
|
|
||||||
module Jobs
|
module Jobs
|
||||||
class NarrativeInit < ::Jobs::Base
|
class NarrativeInit < ::Jobs::Base
|
||||||
sidekiq_options queue: 'critical'
|
sidekiq_options queue: "critical"
|
||||||
|
|
||||||
def execute(args)
|
def execute(args)
|
||||||
if user = User.find_by(id: args[:user_id])
|
if user = User.find_by(id: args[:user_id])
|
||||||
I18n.with_locale(user.effective_locale) do
|
I18n.with_locale(user.effective_locale) { args[:klass].constantize.new.input(:init, user) }
|
||||||
args[:klass].constantize.new.input(:init, user)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,23 +4,24 @@ module Jobs
|
||||||
class SendDefaultWelcomeMessage < ::Jobs::Base
|
class SendDefaultWelcomeMessage < ::Jobs::Base
|
||||||
def execute(args)
|
def execute(args)
|
||||||
if user = User.find_by(id: args[:user_id])
|
if user = User.find_by(id: args[:user_id])
|
||||||
type = user.invited_by ? 'welcome_invite' : 'welcome_user'
|
type = user.invited_by ? "welcome_invite" : "welcome_user"
|
||||||
params = SystemMessage.new(user).defaults
|
params = SystemMessage.new(user).defaults
|
||||||
|
|
||||||
title = I18n.t("system_messages.#{type}.subject_template", params)
|
title = I18n.t("system_messages.#{type}.subject_template", params)
|
||||||
raw = I18n.t("system_messages.#{type}.text_body_template", params)
|
raw = I18n.t("system_messages.#{type}.text_body_template", params)
|
||||||
discobot_user = ::DiscourseNarrativeBot::Base.new.discobot_user
|
discobot_user = ::DiscourseNarrativeBot::Base.new.discobot_user
|
||||||
|
|
||||||
post = PostCreator.create!(
|
post =
|
||||||
discobot_user,
|
PostCreator.create!(
|
||||||
title: title,
|
discobot_user,
|
||||||
raw: raw,
|
title: title,
|
||||||
archetype: Archetype.private_message,
|
raw: raw,
|
||||||
target_usernames: user.username,
|
archetype: Archetype.private_message,
|
||||||
skip_validations: true
|
target_usernames: user.username,
|
||||||
)
|
skip_validations: true,
|
||||||
|
)
|
||||||
|
|
||||||
post.topic.update_status('closed', true, discobot_user)
|
post.topic.update_status("closed", true, discobot_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
discobot_username = 'discobot'
|
discobot_username = "discobot"
|
||||||
|
|
||||||
def seed_primary_email
|
def seed_primary_email
|
||||||
UserEmail.seed do |ue|
|
UserEmail.seed do |ue|
|
||||||
|
@ -42,15 +42,13 @@ bot.create_user_option! if !bot.user_option
|
||||||
|
|
||||||
bot.user_option.update!(
|
bot.user_option.update!(
|
||||||
email_messages_level: UserOption.email_level_types[:never],
|
email_messages_level: UserOption.email_level_types[:never],
|
||||||
email_level: UserOption.email_level_types[:never]
|
email_level: UserOption.email_level_types[:never],
|
||||||
)
|
)
|
||||||
|
|
||||||
bot.create_user_profile! if !bot.user_profile
|
bot.create_user_profile! if !bot.user_profile
|
||||||
|
|
||||||
if !bot.user_profile.bio_raw
|
if !bot.user_profile.bio_raw
|
||||||
bot.user_profile.update!(
|
bot.user_profile.update!(bio_raw: I18n.t("discourse_narrative_bot.bio"))
|
||||||
bio_raw: I18n.t('discourse_narrative_bot.bio')
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Group.user_trust_level_change!(DiscourseNarrativeBot::BOT_USER_ID, TrustLevel[4])
|
Group.user_trust_level_change!(DiscourseNarrativeBot::BOT_USER_ID, TrustLevel[4])
|
||||||
|
|
|
@ -1,41 +1,33 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Badge
|
Badge.where(name: "Complete New User Track").update_all(
|
||||||
.where(name: 'Complete New User Track')
|
name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME,
|
||||||
.update_all(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME)
|
)
|
||||||
|
|
||||||
Badge
|
Badge.where(name: "Complete Discobot Advanced User Track").update_all(
|
||||||
.where(name: 'Complete Discobot Advanced User Track')
|
name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME,
|
||||||
.update_all(name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME)
|
)
|
||||||
|
|
||||||
new_user_narrative_badge = Badge.find_by(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME)
|
new_user_narrative_badge = Badge.find_by(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME)
|
||||||
|
|
||||||
unless new_user_narrative_badge
|
unless new_user_narrative_badge
|
||||||
new_user_narrative_badge = Badge.create!(
|
new_user_narrative_badge =
|
||||||
name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME,
|
Badge.create!(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME, badge_type_id: 3)
|
||||||
badge_type_id: 3
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
advanced_user_narrative_badge = Badge.find_by(name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME)
|
advanced_user_narrative_badge =
|
||||||
|
Badge.find_by(name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME)
|
||||||
|
|
||||||
unless advanced_user_narrative_badge
|
unless advanced_user_narrative_badge
|
||||||
advanced_user_narrative_badge = Badge.create!(
|
advanced_user_narrative_badge =
|
||||||
name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME,
|
Badge.create!(name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME, badge_type_id: 2)
|
||||||
badge_type_id: 2
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
badge_grouping = BadgeGrouping.find(1)
|
badge_grouping = BadgeGrouping.find(1)
|
||||||
|
|
||||||
[
|
[
|
||||||
[new_user_narrative_badge, I18n.t('badges.certified.description')],
|
[new_user_narrative_badge, I18n.t("badges.certified.description")],
|
||||||
[advanced_user_narrative_badge, I18n.t('badges.licensed.description')]
|
[advanced_user_narrative_badge, I18n.t("badges.licensed.description")],
|
||||||
].each do |badge, description|
|
].each do |badge, description|
|
||||||
|
badge.update!(badge_grouping: badge_grouping, description: description, system: true)
|
||||||
badge.update!(
|
|
||||||
badge_grouping: badge_grouping,
|
|
||||||
description: description,
|
|
||||||
system: true
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,18 +23,19 @@ module DiscourseNarrativeBot
|
||||||
topic_id: post.topic_id,
|
topic_id: post.topic_id,
|
||||||
reply_to_post_number: post.post_number,
|
reply_to_post_number: post.post_number,
|
||||||
post_alert_options: defaut_post_alert_opts,
|
post_alert_options: defaut_post_alert_opts,
|
||||||
skip_validations: true
|
skip_validations: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
new_post = PostCreator.create!(self.discobot_user, default_opts.merge(opts))
|
new_post = PostCreator.create!(self.discobot_user, default_opts.merge(opts))
|
||||||
reset_rate_limits(post) if new_post
|
reset_rate_limits(post) if new_post
|
||||||
new_post
|
new_post
|
||||||
else
|
else
|
||||||
PostCreator.create!(self.discobot_user, {
|
PostCreator.create!(
|
||||||
post_alert_options: defaut_post_alert_opts,
|
self.discobot_user,
|
||||||
raw: raw,
|
{ post_alert_options: defaut_post_alert_opts, raw: raw, skip_validations: true }.merge(
|
||||||
skip_validations: true
|
opts,
|
||||||
}.merge(opts))
|
),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,7 +54,8 @@ module DiscourseNarrativeBot
|
||||||
data = DiscourseNarrativeBot::Store.get(user.id.to_s)
|
data = DiscourseNarrativeBot::Store.get(user.id.to_s)
|
||||||
return unless data
|
return unless data
|
||||||
|
|
||||||
key = "#{DiscourseNarrativeBot::PLUGIN_NAME}:reset-rate-limit:#{post.topic_id}:#{data['state']}"
|
key =
|
||||||
|
"#{DiscourseNarrativeBot::PLUGIN_NAME}:reset-rate-limit:#{post.topic_id}:#{data["state"]}"
|
||||||
|
|
||||||
if !(count = Discourse.redis.get(key))
|
if !(count = Discourse.redis.get(key))
|
||||||
count = 0
|
count = 0
|
||||||
|
@ -76,12 +78,14 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
valid = false
|
valid = false
|
||||||
|
|
||||||
doc.css(".mention").each do |mention|
|
doc
|
||||||
if User.normalize_username(mention.text) == "@#{self.discobot_username}"
|
.css(".mention")
|
||||||
valid = true
|
.each do |mention|
|
||||||
break
|
if User.normalize_username(mention.text) == "@#{self.discobot_username}"
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
valid
|
valid
|
||||||
end
|
end
|
||||||
|
@ -94,8 +98,7 @@ module DiscourseNarrativeBot
|
||||||
topic = post.topic
|
topic = post.topic
|
||||||
return false if !topic
|
return false if !topic
|
||||||
|
|
||||||
topic.pm_with_non_human_user? &&
|
topic.pm_with_non_human_user? && topic.topic_allowed_users.where(user_id: -2).exists?
|
||||||
topic.topic_allowed_users.where(user_id: -2).exists?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cancel_timeout_job(user)
|
def cancel_timeout_job(user)
|
||||||
|
@ -107,9 +110,11 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
cancel_timeout_job(user)
|
cancel_timeout_job(user)
|
||||||
|
|
||||||
Jobs.enqueue_in(TIMEOUT_DURATION, :narrative_timeout,
|
Jobs.enqueue_in(
|
||||||
|
TIMEOUT_DURATION,
|
||||||
|
:narrative_timeout,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
klass: self.class.to_s
|
klass: self.class.to_s,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,99 +3,101 @@
|
||||||
module DiscourseNarrativeBot
|
module DiscourseNarrativeBot
|
||||||
class AdvancedUserNarrative < Base
|
class AdvancedUserNarrative < Base
|
||||||
I18N_KEY = "discourse_narrative_bot.advanced_user_narrative".freeze
|
I18N_KEY = "discourse_narrative_bot.advanced_user_narrative".freeze
|
||||||
BADGE_NAME = 'Licensed'.freeze
|
BADGE_NAME = "Licensed".freeze
|
||||||
|
|
||||||
TRANSITION_TABLE = {
|
TRANSITION_TABLE = {
|
||||||
begin: {
|
begin: {
|
||||||
next_state: :tutorial_edit,
|
next_state: :tutorial_edit,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.edit.instructions", i18n_post_args) },
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.edit.instructions", i18n_post_args) },
|
||||||
init: {
|
init: {
|
||||||
action: :start_advanced_track
|
action: :start_advanced_track,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_edit: {
|
tutorial_edit: {
|
||||||
next_state: :tutorial_delete,
|
next_state: :tutorial_delete,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.delete.instructions", i18n_post_args) },
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.delete.instructions", i18n_post_args) },
|
||||||
edit: {
|
edit: {
|
||||||
action: :reply_to_edit
|
action: :reply_to_edit,
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_edit,
|
next_state: :tutorial_edit,
|
||||||
action: :missing_edit
|
action: :missing_edit,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_delete: {
|
tutorial_delete: {
|
||||||
next_state: :tutorial_recover,
|
next_state: :tutorial_recover,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.recover.instructions", i18n_post_args) },
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.recover.instructions", i18n_post_args) },
|
||||||
delete: {
|
delete: {
|
||||||
action: :reply_to_delete
|
action: :reply_to_delete,
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_delete,
|
next_state: :tutorial_delete,
|
||||||
action: :missing_delete
|
action: :missing_delete,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_recover: {
|
tutorial_recover: {
|
||||||
next_state: :tutorial_category_hashtag,
|
next_state: :tutorial_category_hashtag,
|
||||||
next_instructions: Proc.new do
|
next_instructions:
|
||||||
category = Category.secured(Guardian.new(@user)).last
|
Proc.new do
|
||||||
slug = category.slug
|
category = Category.secured(Guardian.new(@user)).last
|
||||||
|
slug = category.slug
|
||||||
|
|
||||||
if parent_category = category.parent_category
|
if parent_category = category.parent_category
|
||||||
slug = "#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{slug}"
|
slug = "#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{slug}"
|
||||||
end
|
end
|
||||||
|
|
||||||
I18n.t("#{I18N_KEY}.category_hashtag.instructions",
|
I18n.t(
|
||||||
i18n_post_args(category: "##{slug}")
|
"#{I18N_KEY}.category_hashtag.instructions",
|
||||||
)
|
i18n_post_args(category: "##{slug}"),
|
||||||
end,
|
)
|
||||||
|
end,
|
||||||
recover: {
|
recover: {
|
||||||
action: :reply_to_recover
|
action: :reply_to_recover,
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_recover,
|
next_state: :tutorial_recover,
|
||||||
action: :missing_recover
|
action: :missing_recover,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_category_hashtag: {
|
tutorial_category_hashtag: {
|
||||||
next_state: :tutorial_change_topic_notification_level,
|
next_state: :tutorial_change_topic_notification_level,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.change_topic_notification_level.instructions", i18n_post_args) },
|
next_instructions:
|
||||||
|
Proc.new do
|
||||||
|
I18n.t("#{I18N_KEY}.change_topic_notification_level.instructions", i18n_post_args)
|
||||||
|
end,
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_category_hashtag
|
action: :reply_to_category_hashtag,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_change_topic_notification_level: {
|
tutorial_change_topic_notification_level: {
|
||||||
next_state: :tutorial_poll,
|
next_state: :tutorial_poll,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.poll.instructions", i18n_post_args) },
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.poll.instructions", i18n_post_args) },
|
||||||
topic_notification_level_changed: {
|
topic_notification_level_changed: {
|
||||||
action: :reply_to_topic_notification_level_changed
|
action: :reply_to_topic_notification_level_changed,
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_change_topic_notification_level,
|
next_state: :tutorial_change_topic_notification_level,
|
||||||
action: :missing_topic_notification_level_change
|
action: :missing_topic_notification_level_change,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_poll: {
|
tutorial_poll: {
|
||||||
prerequisite: Proc.new { SiteSetting.poll_enabled && @user.has_trust_level?(SiteSetting.poll_minimum_trust_level_to_create) },
|
prerequisite:
|
||||||
|
Proc.new do
|
||||||
|
SiteSetting.poll_enabled &&
|
||||||
|
@user.has_trust_level?(SiteSetting.poll_minimum_trust_level_to_create)
|
||||||
|
end,
|
||||||
next_state: :tutorial_details,
|
next_state: :tutorial_details,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.details.instructions", i18n_post_args) },
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.details.instructions", i18n_post_args) },
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_poll
|
action: :reply_to_poll,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_details: {
|
tutorial_details: {
|
||||||
next_state: :end,
|
next_state: :end,
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_details
|
action: :reply_to_details,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def self.badge_name
|
def self.badge_name
|
||||||
|
@ -103,7 +105,7 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reset_trigger
|
def self.reset_trigger
|
||||||
I18n.t('discourse_narrative_bot.advanced_user_narrative.reset_trigger')
|
I18n.t("discourse_narrative_bot.advanced_user_narrative.reset_trigger")
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_bot(user, post)
|
def reset_bot(user, post)
|
||||||
|
@ -123,13 +125,18 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
fake_delay
|
fake_delay
|
||||||
|
|
||||||
post = PostCreator.create!(
|
post =
|
||||||
@user,
|
PostCreator.create!(
|
||||||
raw: I18n.t("#{I18N_KEY}.edit.bot_created_post_raw", i18n_post_args(discobot_username: self.discobot_username)),
|
@user,
|
||||||
topic_id: data[:topic_id],
|
raw:
|
||||||
skip_bot: true,
|
I18n.t(
|
||||||
skip_validations: true
|
"#{I18N_KEY}.edit.bot_created_post_raw",
|
||||||
)
|
i18n_post_args(discobot_username: self.discobot_username),
|
||||||
|
),
|
||||||
|
topic_id: data[:topic_id],
|
||||||
|
skip_bot: true,
|
||||||
|
skip_validations: true,
|
||||||
|
)
|
||||||
|
|
||||||
set_state_data(:post_id, post.id)
|
set_state_data(:post_id, post.id)
|
||||||
post
|
post
|
||||||
|
@ -138,13 +145,18 @@ module DiscourseNarrativeBot
|
||||||
def init_tutorial_recover
|
def init_tutorial_recover
|
||||||
data = get_data(@user)
|
data = get_data(@user)
|
||||||
|
|
||||||
post = PostCreator.create!(
|
post =
|
||||||
@user,
|
PostCreator.create!(
|
||||||
raw: I18n.t("#{I18N_KEY}.recover.deleted_post_raw", i18n_post_args(discobot_username: self.discobot_username)),
|
@user,
|
||||||
topic_id: data[:topic_id],
|
raw:
|
||||||
skip_bot: true,
|
I18n.t(
|
||||||
skip_validations: true
|
"#{I18N_KEY}.recover.deleted_post_raw",
|
||||||
)
|
i18n_post_args(discobot_username: self.discobot_username),
|
||||||
|
),
|
||||||
|
topic_id: data[:topic_id],
|
||||||
|
skip_bot: true,
|
||||||
|
skip_validations: true,
|
||||||
|
)
|
||||||
|
|
||||||
set_state_data(:post_id, post.id)
|
set_state_data(:post_id, post.id)
|
||||||
|
|
||||||
|
@ -172,18 +184,15 @@ module DiscourseNarrativeBot
|
||||||
opts = {
|
opts = {
|
||||||
title: I18n.t("#{I18N_KEY}.title"),
|
title: I18n.t("#{I18N_KEY}.title"),
|
||||||
target_usernames: @user.username,
|
target_usernames: @user.username,
|
||||||
archetype: Archetype.private_message
|
archetype: Archetype.private_message,
|
||||||
}
|
}
|
||||||
|
|
||||||
if @post &&
|
if @post && @post.topic.private_message? &&
|
||||||
@post.topic.private_message? &&
|
@post.topic.topic_allowed_users.pluck(:user_id).include?(@user.id)
|
||||||
@post.topic.topic_allowed_users.pluck(:user_id).include?(@user.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if @data[:topic_id]
|
if @data[:topic_id]
|
||||||
opts = opts
|
opts = opts.merge(topic_id: @data[:topic_id]).except(:title, :target_usernames, :archetype)
|
||||||
.merge(topic_id: @data[:topic_id])
|
|
||||||
.except(:title, :target_usernames, :archetype)
|
|
||||||
end
|
end
|
||||||
post = reply_to(@post, raw, opts)
|
post = reply_to(@post, raw, opts)
|
||||||
|
|
||||||
|
@ -213,9 +222,10 @@ module DiscourseNarrativeBot
|
||||||
fake_delay
|
fake_delay
|
||||||
|
|
||||||
unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.edit.not_found",
|
reply_to(
|
||||||
i18n_post_args(url: Post.find_by(id: post_id).url)
|
@post,
|
||||||
))
|
I18n.t("#{I18N_KEY}.edit.not_found", i18n_post_args(url: Post.find_by(id: post_id).url)),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
|
@ -233,16 +243,15 @@ module DiscourseNarrativeBot
|
||||||
#{instance_eval(&@next_instructions)}
|
#{instance_eval(&@next_instructions)}
|
||||||
MD
|
MD
|
||||||
|
|
||||||
PostCreator.create!(self.discobot_user,
|
PostCreator.create!(self.discobot_user, raw: raw, topic_id: @topic_id)
|
||||||
raw: raw,
|
|
||||||
topic_id: @topic_id
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def missing_delete
|
def missing_delete
|
||||||
return unless valid_topic?(@post.topic_id)
|
return unless valid_topic?(@post.topic_id)
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.delete.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.delete.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -258,18 +267,19 @@ module DiscourseNarrativeBot
|
||||||
#{instance_eval(&@next_instructions)}
|
#{instance_eval(&@next_instructions)}
|
||||||
MD
|
MD
|
||||||
|
|
||||||
PostCreator.create!(self.discobot_user,
|
PostCreator.create!(self.discobot_user, raw: raw, topic_id: @post.topic_id)
|
||||||
raw: raw,
|
|
||||||
topic_id: @post.topic_id
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def missing_recover
|
def missing_recover
|
||||||
return unless valid_topic?(@post.topic_id) &&
|
unless valid_topic?(@post.topic_id) &&
|
||||||
post_id = get_state_data(:post_id) && @post.id != post_id
|
post_id = get_state_data(:post_id) && @post.id != post_id
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.recover.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.recover.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -278,7 +288,7 @@ module DiscourseNarrativeBot
|
||||||
topic_id = @post.topic_id
|
topic_id = @post.topic_id
|
||||||
return unless valid_topic?(topic_id)
|
return unless valid_topic?(topic_id)
|
||||||
|
|
||||||
if Nokogiri::HTML5.fragment(@post.cooked).css('.hashtag').size > 0
|
if Nokogiri::HTML5.fragment(@post.cooked).css(".hashtag").size > 0
|
||||||
raw = <<~MD
|
raw = <<~MD
|
||||||
#{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)}
|
#{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)}
|
||||||
|
|
||||||
|
@ -289,7 +299,9 @@ module DiscourseNarrativeBot
|
||||||
reply_to(@post, raw)
|
reply_to(@post, raw)
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.category_hashtag.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.category_hashtag.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -299,7 +311,12 @@ module DiscourseNarrativeBot
|
||||||
return unless valid_topic?(@post.topic_id)
|
return unless valid_topic?(@post.topic_id)
|
||||||
|
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.change_topic_notification_level.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(
|
||||||
|
@post,
|
||||||
|
I18n.t("#{I18N_KEY}.change_topic_notification_level.not_found", i18n_post_args),
|
||||||
|
)
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -316,10 +333,7 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
fake_delay
|
fake_delay
|
||||||
|
|
||||||
post = PostCreator.create!(self.discobot_user,
|
post = PostCreator.create!(self.discobot_user, raw: raw, topic_id: @topic_id)
|
||||||
raw: raw,
|
|
||||||
topic_id: @topic_id
|
|
||||||
)
|
|
||||||
|
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
post
|
post
|
||||||
|
@ -340,7 +354,9 @@ module DiscourseNarrativeBot
|
||||||
reply_to(@post, raw)
|
reply_to(@post, raw)
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.poll.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.poll.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -355,7 +371,9 @@ module DiscourseNarrativeBot
|
||||||
if Nokogiri::HTML5.fragment(@post.cooked).css("details").size > 0
|
if Nokogiri::HTML5.fragment(@post.cooked).css("details").size > 0
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.details.reply", i18n_post_args))
|
reply_to(@post, I18n.t("#{I18N_KEY}.details.reply", i18n_post_args))
|
||||||
else
|
else
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.details.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.details.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -370,7 +388,9 @@ module DiscourseNarrativeBot
|
||||||
if @post.wiki
|
if @post.wiki
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.reply", i18n_post_args))
|
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.reply", i18n_post_args))
|
||||||
else
|
else
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -379,9 +399,10 @@ module DiscourseNarrativeBot
|
||||||
def end_reply
|
def end_reply
|
||||||
fake_delay
|
fake_delay
|
||||||
|
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.end.message",
|
reply_to(
|
||||||
i18n_post_args(certificate: certificate('advanced'))
|
@post,
|
||||||
))
|
I18n.t("#{I18N_KEY}.end.message", i18n_post_args(certificate: certificate("advanced"))),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def synchronize(user)
|
def synchronize(user)
|
||||||
|
|
|
@ -4,7 +4,8 @@ module DiscourseNarrativeBot
|
||||||
class Base
|
class Base
|
||||||
include Actions
|
include Actions
|
||||||
|
|
||||||
class InvalidTransitionError < StandardError; end
|
class InvalidTransitionError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
def input(input, user, post: nil, topic_id: nil, skip: false)
|
def input(input, user, post: nil, topic_id: nil, skip: false)
|
||||||
new_post = nil
|
new_post = nil
|
||||||
|
@ -30,16 +31,18 @@ module DiscourseNarrativeBot
|
||||||
next_opts = self.class::TRANSITION_TABLE.fetch(next_state)
|
next_opts = self.class::TRANSITION_TABLE.fetch(next_state)
|
||||||
prerequisite = next_opts[:prerequisite]
|
prerequisite = next_opts[:prerequisite]
|
||||||
|
|
||||||
if (!prerequisite || instance_eval(&prerequisite)) && !(
|
if (!prerequisite || instance_eval(&prerequisite)) &&
|
||||||
SiteSetting.discourse_narrative_bot_skip_tutorials.present? &&
|
!(
|
||||||
SiteSetting.discourse_narrative_bot_skip_tutorials.split("|").include?(next_state.to_s))
|
SiteSetting.discourse_narrative_bot_skip_tutorials.present? &&
|
||||||
|
SiteSetting
|
||||||
|
.discourse_narrative_bot_skip_tutorials
|
||||||
|
.split("|")
|
||||||
|
.include?(next_state.to_s)
|
||||||
|
)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
[:next_state, :next_instructions].each do |key|
|
%i[next_state next_instructions].each { |key| opts[key] = next_opts[key] }
|
||||||
opts[key] = next_opts[key]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
rescue InvalidTransitionError
|
rescue InvalidTransitionError
|
||||||
# For given input, no transition for current state
|
# For given input, no transition for current state
|
||||||
|
@ -78,16 +81,9 @@ module DiscourseNarrativeBot
|
||||||
end_reply
|
end_reply
|
||||||
cancel_timeout_job(user)
|
cancel_timeout_job(user)
|
||||||
|
|
||||||
BadgeGranter.grant(
|
BadgeGranter.grant(Badge.find_by(name: self.class.badge_name), user)
|
||||||
Badge.find_by(name: self.class.badge_name),
|
|
||||||
user
|
|
||||||
)
|
|
||||||
|
|
||||||
set_data(@user,
|
set_data(@user, topic_id: new_post.topic_id, state: :end, track: self.class.to_s)
|
||||||
topic_id: new_post.topic_id,
|
|
||||||
state: :end,
|
|
||||||
track: self.class.to_s
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
|
@ -116,25 +112,29 @@ module DiscourseNarrativeBot
|
||||||
@data = get_data(user) || {}
|
@data = get_data(user) || {}
|
||||||
|
|
||||||
if post = Post.find_by(id: @data[:last_post_id])
|
if post = Post.find_by(id: @data[:last_post_id])
|
||||||
reply_to(post, I18n.t("discourse_narrative_bot.timeout.message",
|
reply_to(
|
||||||
i18n_post_args(
|
post,
|
||||||
username: user.username,
|
I18n.t(
|
||||||
skip_trigger: TrackSelector.skip_trigger,
|
"discourse_narrative_bot.timeout.message",
|
||||||
reset_trigger: "#{TrackSelector.reset_trigger} #{self.class.reset_trigger}"
|
i18n_post_args(
|
||||||
)
|
username: user.username,
|
||||||
), {}, skip_send_email: false)
|
skip_trigger: TrackSelector.skip_trigger,
|
||||||
|
reset_trigger: "#{TrackSelector.reset_trigger} #{self.class.reset_trigger}",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
skip_send_email: false,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def certificate(type = nil)
|
def certificate(type = nil)
|
||||||
options = {
|
options = { user_id: @user.id, date: Time.zone.now.strftime("%b %d %Y"), format: :svg }
|
||||||
user_id: @user.id,
|
|
||||||
date: Time.zone.now.strftime('%b %d %Y'),
|
|
||||||
format: :svg
|
|
||||||
}
|
|
||||||
options.merge!(type: type) if type
|
options.merge!(type: type) if type
|
||||||
|
|
||||||
src = Discourse.base_url + DiscourseNarrativeBot::Engine.routes.url_helpers.certificate_path(options)
|
src =
|
||||||
|
Discourse.base_url +
|
||||||
|
DiscourseNarrativeBot::Engine.routes.url_helpers.certificate_path(options)
|
||||||
alt = CGI.escapeHTML(I18n.t("#{self.class::I18N_KEY}.certificate.alt"))
|
alt = CGI.escapeHTML(I18n.t("#{self.class::I18N_KEY}.certificate.alt"))
|
||||||
|
|
||||||
"<iframe class='discobot-certificate' src='#{src}' width='650' height='464' alt='#{alt}'></iframe>"
|
"<iframe class='discobot-certificate' src='#{src}' width='650' height='464' alt='#{alt}'></iframe>"
|
||||||
|
@ -192,7 +192,7 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_implemented
|
def not_implemented
|
||||||
raise 'Not implemented.'
|
raise "Not implemented."
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -10,7 +10,7 @@ module DiscourseNarrativeBot
|
||||||
begin
|
begin
|
||||||
Date.parse(date)
|
Date.parse(date)
|
||||||
rescue ArgumentError => e
|
rescue ArgumentError => e
|
||||||
if e.message == 'invalid date'
|
if e.message == "invalid date"
|
||||||
Date.parse(Date.today.to_s)
|
Date.parse(Date.today.to_s)
|
||||||
else
|
else
|
||||||
raise e
|
raise e
|
||||||
|
@ -25,14 +25,20 @@ module DiscourseNarrativeBot
|
||||||
svg_default_width = 538.583
|
svg_default_width = 538.583
|
||||||
logo_container = logo_group(55, svg_default_width, 280)
|
logo_container = logo_group(55, svg_default_width, 280)
|
||||||
|
|
||||||
ApplicationController.render(inline: read_template('new_user'), assigns: assign_options(svg_default_width, logo_container))
|
ApplicationController.render(
|
||||||
|
inline: read_template("new_user"),
|
||||||
|
assigns: assign_options(svg_default_width, logo_container),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def advanced_user_track
|
def advanced_user_track
|
||||||
svg_default_width = 722.8
|
svg_default_width = 722.8
|
||||||
logo_container = logo_group(40, svg_default_width, 350)
|
logo_container = logo_group(40, svg_default_width, 350)
|
||||||
|
|
||||||
ApplicationController.render(inline: read_template('advanced_user'), assigns: assign_options(svg_default_width, logo_container))
|
ApplicationController.render(
|
||||||
|
inline: read_template("advanced_user"),
|
||||||
|
assigns: assign_options(svg_default_width, logo_container),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -48,7 +54,7 @@ module DiscourseNarrativeBot
|
||||||
date: @date,
|
date: @date,
|
||||||
avatar_url: @avatar_url,
|
avatar_url: @avatar_url,
|
||||||
logo_group: logo_group,
|
logo_group: logo_group,
|
||||||
name: name
|
name: name,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,25 +7,26 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
def self.roll(num_of_dice, range_of_dice)
|
def self.roll(num_of_dice, range_of_dice)
|
||||||
if num_of_dice == 0 || range_of_dice == 0
|
if num_of_dice == 0 || range_of_dice == 0
|
||||||
return I18n.t('discourse_narrative_bot.dice.invalid')
|
return I18n.t("discourse_narrative_bot.dice.invalid")
|
||||||
end
|
end
|
||||||
|
|
||||||
output = +''
|
output = +""
|
||||||
|
|
||||||
if num_of_dice > MAXIMUM_NUM_OF_DICE
|
if num_of_dice > MAXIMUM_NUM_OF_DICE
|
||||||
output << I18n.t('discourse_narrative_bot.dice.not_enough_dice', count: MAXIMUM_NUM_OF_DICE)
|
output << I18n.t("discourse_narrative_bot.dice.not_enough_dice", count: MAXIMUM_NUM_OF_DICE)
|
||||||
output << "\n\n"
|
output << "\n\n"
|
||||||
num_of_dice = MAXIMUM_NUM_OF_DICE
|
num_of_dice = MAXIMUM_NUM_OF_DICE
|
||||||
end
|
end
|
||||||
|
|
||||||
if range_of_dice > MAXIMUM_RANGE_OF_DICE
|
if range_of_dice > MAXIMUM_RANGE_OF_DICE
|
||||||
output << I18n.t('discourse_narrative_bot.dice.out_of_range')
|
output << I18n.t("discourse_narrative_bot.dice.out_of_range")
|
||||||
output << "\n\n"
|
output << "\n\n"
|
||||||
range_of_dice = MAXIMUM_RANGE_OF_DICE
|
range_of_dice = MAXIMUM_RANGE_OF_DICE
|
||||||
end
|
end
|
||||||
|
|
||||||
output << I18n.t('discourse_narrative_bot.dice.results',
|
output << I18n.t(
|
||||||
results: num_of_dice.times.map { rand(1..range_of_dice) }.join(", ")
|
"discourse_narrative_bot.dice.results",
|
||||||
|
results: num_of_dice.times.map { rand(1..range_of_dice) }.join(", "),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
module DiscourseNarrativeBot
|
module DiscourseNarrativeBot
|
||||||
class Magic8Ball
|
class Magic8Ball
|
||||||
def self.generate_answer
|
def self.generate_answer
|
||||||
I18n.t("discourse_narrative_bot.magic_8_ball.result", result: I18n.t(
|
I18n.t(
|
||||||
"discourse_narrative_bot.magic_8_ball.answers.#{rand(1..20)}"
|
"discourse_narrative_bot.magic_8_ball.result",
|
||||||
))
|
result: I18n.t("discourse_narrative_bot.magic_8_ball.answers.#{rand(1..20)}"),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,136 +1,136 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'distributed_mutex'
|
require "distributed_mutex"
|
||||||
|
|
||||||
module DiscourseNarrativeBot
|
module DiscourseNarrativeBot
|
||||||
class NewUserNarrative < Base
|
class NewUserNarrative < Base
|
||||||
I18N_KEY = "discourse_narrative_bot.new_user_narrative".freeze
|
I18N_KEY = "discourse_narrative_bot.new_user_narrative".freeze
|
||||||
BADGE_NAME = 'Certified'.freeze
|
BADGE_NAME = "Certified".freeze
|
||||||
|
|
||||||
TRANSITION_TABLE = {
|
TRANSITION_TABLE = {
|
||||||
begin: {
|
begin: {
|
||||||
init: {
|
init: {
|
||||||
next_state: :tutorial_bookmark,
|
next_state: :tutorial_bookmark,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.bookmark.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
action: :say_hello
|
Proc.new { I18n.t("#{I18N_KEY}.bookmark.instructions", base_uri: Discourse.base_path) },
|
||||||
}
|
action: :say_hello,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_bookmark: {
|
tutorial_bookmark: {
|
||||||
next_state: :tutorial_onebox,
|
next_state: :tutorial_onebox,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.onebox.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.onebox.instructions", base_uri: Discourse.base_path) },
|
||||||
bookmark: {
|
bookmark: {
|
||||||
action: :reply_to_bookmark
|
action: :reply_to_bookmark,
|
||||||
},
|
},
|
||||||
|
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_bookmark,
|
next_state: :tutorial_bookmark,
|
||||||
action: :missing_bookmark
|
action: :missing_bookmark,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_onebox: {
|
tutorial_onebox: {
|
||||||
next_state: :tutorial_emoji,
|
next_state: :tutorial_emoji,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.emoji.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.emoji.instructions", base_uri: Discourse.base_path) },
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_onebox
|
action: :reply_to_onebox,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_emoji: {
|
tutorial_emoji: {
|
||||||
prerequisite: Proc.new { SiteSetting.enable_emoji },
|
prerequisite: Proc.new { SiteSetting.enable_emoji },
|
||||||
next_state: :tutorial_mention,
|
next_state: :tutorial_mention,
|
||||||
next_instructions: Proc.new {
|
next_instructions:
|
||||||
I18n.t("#{I18N_KEY}.mention.instructions",
|
Proc.new do
|
||||||
discobot_username: self.discobot_username,
|
I18n.t(
|
||||||
base_uri: Discourse.base_path)
|
"#{I18N_KEY}.mention.instructions",
|
||||||
},
|
discobot_username: self.discobot_username,
|
||||||
|
base_uri: Discourse.base_path,
|
||||||
|
)
|
||||||
|
end,
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_emoji
|
action: :reply_to_emoji,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_mention: {
|
tutorial_mention: {
|
||||||
prerequisite: Proc.new { SiteSetting.enable_mentions },
|
prerequisite: Proc.new { SiteSetting.enable_mentions },
|
||||||
next_state: :tutorial_formatting,
|
next_state: :tutorial_formatting,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.formatting.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.formatting.instructions", base_uri: Discourse.base_path) },
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_mention
|
action: :reply_to_mention,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_formatting: {
|
tutorial_formatting: {
|
||||||
next_state: :tutorial_quote,
|
next_state: :tutorial_quote,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.quoting.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.quoting.instructions", base_uri: Discourse.base_path) },
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_formatting
|
action: :reply_to_formatting,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_quote: {
|
tutorial_quote: {
|
||||||
next_state: :tutorial_images,
|
next_state: :tutorial_images,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.images.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.images.instructions", base_uri: Discourse.base_path) },
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_quote
|
action: :reply_to_quote,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
# Note: tutorial_images and tutorial_likes are mutually exclusive.
|
# Note: tutorial_images and tutorial_likes are mutually exclusive.
|
||||||
# The prerequisites should ensure only one of them is called.
|
# The prerequisites should ensure only one of them is called.
|
||||||
tutorial_images: {
|
tutorial_images: {
|
||||||
prerequisite: Proc.new { @user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) },
|
prerequisite:
|
||||||
|
Proc.new { @user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) },
|
||||||
next_state: :tutorial_likes,
|
next_state: :tutorial_likes,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.likes.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.likes.instructions", base_uri: Discourse.base_path) },
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_image
|
action: :reply_to_image,
|
||||||
},
|
},
|
||||||
like: {
|
like: {
|
||||||
action: :track_images_like
|
action: :track_images_like,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_likes: {
|
tutorial_likes: {
|
||||||
prerequisite: Proc.new { !@user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) },
|
prerequisite:
|
||||||
|
Proc.new { !@user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) },
|
||||||
next_state: :tutorial_flag,
|
next_state: :tutorial_flag,
|
||||||
next_instructions: Proc.new {
|
next_instructions:
|
||||||
I18n.t("#{I18N_KEY}.flag.instructions",
|
Proc.new do
|
||||||
guidelines_url: url_helpers(:guidelines_url),
|
I18n.t(
|
||||||
about_url: url_helpers(:about_index_url),
|
"#{I18N_KEY}.flag.instructions",
|
||||||
base_uri: Discourse.base_path)
|
guidelines_url: url_helpers(:guidelines_url),
|
||||||
},
|
about_url: url_helpers(:about_index_url),
|
||||||
|
base_uri: Discourse.base_path,
|
||||||
|
)
|
||||||
|
end,
|
||||||
like: {
|
like: {
|
||||||
action: :reply_to_likes
|
action: :reply_to_likes,
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_likes,
|
next_state: :tutorial_likes,
|
||||||
action: :missing_likes_like
|
action: :missing_likes_like,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_flag: {
|
tutorial_flag: {
|
||||||
prerequisite: Proc.new { SiteSetting.allow_flagging_staff },
|
prerequisite: Proc.new { SiteSetting.allow_flagging_staff },
|
||||||
next_state: :tutorial_search,
|
next_state: :tutorial_search,
|
||||||
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.search.instructions", base_uri: Discourse.base_path) },
|
next_instructions:
|
||||||
|
Proc.new { I18n.t("#{I18N_KEY}.search.instructions", base_uri: Discourse.base_path) },
|
||||||
flag: {
|
flag: {
|
||||||
action: :reply_to_flag
|
action: :reply_to_flag,
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
next_state: :tutorial_flag,
|
next_state: :tutorial_flag,
|
||||||
action: :missing_flag
|
action: :missing_flag,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
tutorial_search: {
|
tutorial_search: {
|
||||||
next_state: :end,
|
next_state: :end,
|
||||||
reply: {
|
reply: {
|
||||||
action: :reply_to_search
|
action: :reply_to_search,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def self.badge_name
|
def self.badge_name
|
||||||
|
@ -138,7 +138,7 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.search_answer
|
def self.search_answer
|
||||||
':herb:'
|
":herb:"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.search_answer_emoji
|
def self.search_answer_emoji
|
||||||
|
@ -146,7 +146,7 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reset_trigger
|
def self.reset_trigger
|
||||||
I18n.t('discourse_narrative_bot.new_user_narrative.reset_trigger')
|
I18n.t("discourse_narrative_bot.new_user_narrative.reset_trigger")
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_bot(user, post)
|
def reset_bot(user, post)
|
||||||
|
@ -173,7 +173,7 @@ module DiscourseNarrativeBot
|
||||||
topic = @post.topic
|
topic = @post.topic
|
||||||
post = topic.first_post
|
post = topic.first_post
|
||||||
|
|
||||||
MessageBus.publish('/new_user_narrative/tutorial_search', {}, user_ids: [@user.id])
|
MessageBus.publish("/new_user_narrative/tutorial_search", {}, user_ids: [@user.id])
|
||||||
|
|
||||||
raw = <<~MD
|
raw = <<~MD
|
||||||
#{post.raw}
|
#{post.raw}
|
||||||
|
@ -184,7 +184,8 @@ module DiscourseNarrativeBot
|
||||||
PostRevisor.new(post, topic).revise!(
|
PostRevisor.new(post, topic).revise!(
|
||||||
self.discobot_user,
|
self.discobot_user,
|
||||||
{ raw: raw },
|
{ raw: raw },
|
||||||
skip_validations: true, force_new_version: true
|
skip_validations: true,
|
||||||
|
force_new_version: true,
|
||||||
)
|
)
|
||||||
|
|
||||||
set_state_data(:post_version, post.reload.version || 0)
|
set_state_data(:post_version, post.reload.version || 0)
|
||||||
|
@ -198,13 +199,11 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def say_hello
|
def say_hello
|
||||||
raw = I18n.t(
|
raw =
|
||||||
"#{I18N_KEY}.hello.message",
|
I18n.t(
|
||||||
i18n_post_args(
|
"#{I18N_KEY}.hello.message",
|
||||||
username: @user.username,
|
i18n_post_args(username: @user.username, title: SiteSetting.title),
|
||||||
title: SiteSetting.title
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
raw = <<~MD
|
raw = <<~MD
|
||||||
#{raw}
|
#{raw}
|
||||||
|
@ -213,9 +212,7 @@ module DiscourseNarrativeBot
|
||||||
MD
|
MD
|
||||||
|
|
||||||
title = I18n.t("#{I18N_KEY}.hello.title", title: SiteSetting.title)
|
title = I18n.t("#{I18N_KEY}.hello.title", title: SiteSetting.title)
|
||||||
if SiteSetting.max_emojis_in_title == 0
|
title = title.gsub(/:([\w\-+]+(?::t\d)?):/, "").strip if SiteSetting.max_emojis_in_title == 0
|
||||||
title = title.gsub(/:([\w\-+]+(?::t\d)?):/, '').strip
|
|
||||||
end
|
|
||||||
|
|
||||||
opts = {
|
opts = {
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -224,17 +221,13 @@ module DiscourseNarrativeBot
|
||||||
subtype: TopicSubtype.system_message,
|
subtype: TopicSubtype.system_message,
|
||||||
}
|
}
|
||||||
|
|
||||||
if @post &&
|
if @post && @post.topic.private_message? &&
|
||||||
@post.topic.private_message? &&
|
@post.topic.topic_allowed_users.pluck(:user_id).include?(@user.id)
|
||||||
@post.topic.topic_allowed_users.pluck(:user_id).include?(@user.id)
|
|
||||||
|
|
||||||
opts = opts.merge(topic_id: @post.topic_id)
|
opts = opts.merge(topic_id: @post.topic_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @data[:topic_id]
|
if @data[:topic_id]
|
||||||
opts = opts
|
opts = opts.merge(topic_id: @data[:topic_id]).except(:title, :target_usernames, :archetype)
|
||||||
.merge(topic_id: @data[:topic_id])
|
|
||||||
.except(:title, :target_usernames, :archetype)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
post = reply_to(@post, raw, opts)
|
post = reply_to(@post, raw, opts)
|
||||||
|
@ -249,7 +242,9 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
fake_delay
|
fake_delay
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.bookmark.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.bookmark.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -292,7 +287,9 @@ module DiscourseNarrativeBot
|
||||||
reply
|
reply
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.onebox.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.onebox.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -302,11 +299,12 @@ module DiscourseNarrativeBot
|
||||||
post_topic_id = @post.topic_id
|
post_topic_id = @post.topic_id
|
||||||
return unless valid_topic?(post_topic_id)
|
return unless valid_topic?(post_topic_id)
|
||||||
|
|
||||||
post_liked = PostAction.exists?(
|
post_liked =
|
||||||
post_action_type_id: PostActionType.types[:like],
|
PostAction.exists?(
|
||||||
post_id: @data[:last_post_id],
|
post_action_type_id: PostActionType.types[:like],
|
||||||
user_id: @user.id
|
post_id: @data[:last_post_id],
|
||||||
)
|
user_id: @user.id,
|
||||||
|
)
|
||||||
|
|
||||||
if post_liked
|
if post_liked
|
||||||
set_state_data(:liked, true)
|
set_state_data(:liked, true)
|
||||||
|
@ -358,18 +356,23 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
like_post(@post)
|
like_post(@post)
|
||||||
else
|
else
|
||||||
raw = I18n.t(
|
raw =
|
||||||
"#{I18N_KEY}.images.like_not_found",
|
I18n.t(
|
||||||
i18n_post_args(url: Post.find_by(id: @data[:last_post_id]).url)
|
"#{I18N_KEY}.images.like_not_found",
|
||||||
)
|
i18n_post_args(url: Post.find_by(id: @data[:last_post_id]).url),
|
||||||
|
)
|
||||||
|
|
||||||
transition = false
|
transition = false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raw = I18n.t(
|
raw =
|
||||||
"#{I18N_KEY}.images.not_found",
|
I18n.t(
|
||||||
i18n_post_args(image_url: "#{Discourse.base_url}/plugins/discourse-narrative-bot/images/dog-walk.gif")
|
"#{I18N_KEY}.images.not_found",
|
||||||
)
|
i18n_post_args(
|
||||||
|
image_url:
|
||||||
|
"#{Discourse.base_url}/plugins/discourse-narrative-bot/images/dog-walk.gif",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
transition = false
|
transition = false
|
||||||
end
|
end
|
||||||
|
@ -398,11 +401,12 @@ module DiscourseNarrativeBot
|
||||||
post_topic_id = @post.topic_id
|
post_topic_id = @post.topic_id
|
||||||
return unless valid_topic?(post_topic_id)
|
return unless valid_topic?(post_topic_id)
|
||||||
|
|
||||||
post_liked = PostAction.exists?(
|
post_liked =
|
||||||
post_action_type_id: PostActionType.types[:like],
|
PostAction.exists?(
|
||||||
post_id: @data[:last_post_id],
|
post_action_type_id: PostActionType.types[:like],
|
||||||
user_id: @user.id
|
post_id: @data[:last_post_id],
|
||||||
)
|
user_id: @user.id,
|
||||||
|
)
|
||||||
|
|
||||||
if post_liked
|
if post_liked
|
||||||
raw = <<~MD
|
raw = <<~MD
|
||||||
|
@ -425,7 +429,10 @@ module DiscourseNarrativeBot
|
||||||
post_topic_id = @post.topic_id
|
post_topic_id = @post.topic_id
|
||||||
return unless valid_topic?(post_topic_id)
|
return unless valid_topic?(post_topic_id)
|
||||||
|
|
||||||
if Nokogiri::HTML5.fragment(@post.cooked).css("b", "strong", "em", "i", ".bbcode-i", ".bbcode-b").size > 0
|
if Nokogiri::HTML5
|
||||||
|
.fragment(@post.cooked)
|
||||||
|
.css("b", "strong", "em", "i", ".bbcode-i", ".bbcode-b")
|
||||||
|
.size > 0
|
||||||
raw = <<~MD
|
raw = <<~MD
|
||||||
#{I18n.t("#{I18N_KEY}.formatting.reply", i18n_post_args)}
|
#{I18n.t("#{I18N_KEY}.formatting.reply", i18n_post_args)}
|
||||||
|
|
||||||
|
@ -439,7 +446,9 @@ module DiscourseNarrativeBot
|
||||||
reply
|
reply
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.formatting.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.formatting.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -465,7 +474,9 @@ module DiscourseNarrativeBot
|
||||||
reply
|
reply
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.quoting.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.quoting.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -491,7 +502,9 @@ module DiscourseNarrativeBot
|
||||||
reply
|
reply
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.emoji.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.emoji.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -517,13 +530,13 @@ module DiscourseNarrativeBot
|
||||||
fake_delay
|
fake_delay
|
||||||
|
|
||||||
unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
reply_to(@post, I18n.t(
|
reply_to(
|
||||||
"#{I18N_KEY}.mention.not_found",
|
@post,
|
||||||
i18n_post_args(
|
I18n.t(
|
||||||
username: @user.username,
|
"#{I18N_KEY}.mention.not_found",
|
||||||
discobot_username: self.discobot_username
|
i18n_post_args(username: @user.username, discobot_username: self.discobot_username),
|
||||||
)
|
),
|
||||||
))
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
|
@ -536,9 +549,13 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
# Remove any incorrect flags so that they can try again
|
# Remove any incorrect flags so that they can try again
|
||||||
if @post.user_id == -2
|
if @post.user_id == -2
|
||||||
@post.post_actions
|
@post
|
||||||
|
.post_actions
|
||||||
.where(user_id: @user.id)
|
.where(user_id: @user.id)
|
||||||
.where("post_action_type_id IN (?)", (PostActionType.flag_types.values - [PostActionType.types[:inappropriate]]))
|
.where(
|
||||||
|
"post_action_type_id IN (?)",
|
||||||
|
(PostActionType.flag_types.values - [PostActionType.types[:inappropriate]]),
|
||||||
|
)
|
||||||
.destroy_all
|
.destroy_all
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -571,12 +588,18 @@ module DiscourseNarrativeBot
|
||||||
post_topic_id = @post.topic_id
|
post_topic_id = @post.topic_id
|
||||||
return unless valid_topic?(post_topic_id)
|
return unless valid_topic?(post_topic_id)
|
||||||
|
|
||||||
if @post.raw.include?(NewUserNarrative.search_answer) || @post.raw.include?(NewUserNarrative.search_answer_emoji)
|
if @post.raw.include?(NewUserNarrative.search_answer) ||
|
||||||
|
@post.raw.include?(NewUserNarrative.search_answer_emoji)
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.search.reply", i18n_post_args(search_url: url_helpers(:search_url))))
|
reply_to(
|
||||||
|
@post,
|
||||||
|
I18n.t("#{I18N_KEY}.search.reply", i18n_post_args(search_url: url_helpers(:search_url))),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
fake_delay
|
fake_delay
|
||||||
reply_to(@post, I18n.t("#{I18N_KEY}.search.not_found", i18n_post_args)) unless @data[:attempted]
|
unless @data[:attempted]
|
||||||
|
reply_to(@post, I18n.t("#{I18N_KEY}.search.not_found", i18n_post_args))
|
||||||
|
end
|
||||||
enqueue_timeout_job(@user)
|
enqueue_timeout_job(@user)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -587,16 +610,17 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
reply_to(
|
reply_to(
|
||||||
@post,
|
@post,
|
||||||
I18n.t("#{I18N_KEY}.end.message",
|
I18n.t(
|
||||||
|
"#{I18N_KEY}.end.message",
|
||||||
i18n_post_args(
|
i18n_post_args(
|
||||||
username: @user.username,
|
username: @user.username,
|
||||||
base_url: Discourse.base_url,
|
base_url: Discourse.base_url,
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
discobot_username: self.discobot_username,
|
discobot_username: self.discobot_username,
|
||||||
advanced_trigger: AdvancedUserNarrative.reset_trigger
|
advanced_trigger: AdvancedUserNarrative.reset_trigger,
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
topic_id: @data[:topic_id]
|
topic_id: @data[:topic_id],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -605,15 +629,12 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def welcome_topic
|
def welcome_topic
|
||||||
Topic.find_by(slug: 'welcome-to-discourse', archetype: Archetype.default) ||
|
Topic.find_by(slug: "welcome-to-discourse", archetype: Archetype.default) ||
|
||||||
Topic.recent(1).first
|
Topic.recent(1).first
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_helpers(url, opts = {})
|
def url_helpers(url, opts = {})
|
||||||
Rails.application.routes.url_helpers.public_send(
|
Rails.application.routes.url_helpers.public_send(url, opts.merge(host: Discourse.base_url))
|
||||||
url,
|
|
||||||
opts.merge(host: Discourse.base_url)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'excon'
|
require "excon"
|
||||||
|
|
||||||
module DiscourseNarrativeBot
|
module DiscourseNarrativeBot
|
||||||
class QuoteGenerator
|
class QuoteGenerator
|
||||||
API_ENDPOINT = 'http://api.forismatic.com/api/1.0/'.freeze
|
API_ENDPOINT = "http://api.forismatic.com/api/1.0/".freeze
|
||||||
|
|
||||||
def self.format_quote(quote, author)
|
def self.format_quote(quote, author)
|
||||||
I18n.t('discourse_narrative_bot.quote.results', quote: quote, author: author)
|
I18n.t("discourse_narrative_bot.quote.results", quote: quote, author: author)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.generate(user)
|
def self.generate(user)
|
||||||
quote, author =
|
quote, author =
|
||||||
if !user.effective_locale.start_with?('en')
|
if !user.effective_locale.start_with?("en")
|
||||||
translation_key = "discourse_narrative_bot.quote.#{rand(1..10)}"
|
translation_key = "discourse_narrative_bot.quote.#{rand(1..10)}"
|
||||||
|
|
||||||
[
|
[I18n.t("#{translation_key}.quote"), I18n.t("#{translation_key}.author")]
|
||||||
I18n.t("#{translation_key}.quote"),
|
|
||||||
I18n.t("#{translation_key}.author")
|
|
||||||
]
|
|
||||||
else
|
else
|
||||||
connection = Excon.new("#{API_ENDPOINT}?lang=en&format=json&method=getQuote")
|
connection = Excon.new("#{API_ENDPOINT}?lang=en&format=json&method=getQuote")
|
||||||
response = connection.request(expects: [200, 201], method: :Get)
|
response = connection.request(expects: [200, 201], method: :Get)
|
||||||
|
|
|
@ -4,18 +4,12 @@ module DiscourseNarrativeBot
|
||||||
class TrackSelector
|
class TrackSelector
|
||||||
include Actions
|
include Actions
|
||||||
|
|
||||||
GENERIC_REPLIES_COUNT_PREFIX = 'discourse-narrative-bot:track-selector-count:'.freeze
|
GENERIC_REPLIES_COUNT_PREFIX = "discourse-narrative-bot:track-selector-count:".freeze
|
||||||
PUBLIC_DISPLAY_BOT_HELP_KEY = 'discourse-narrative-bot:track-selector:display-bot-help'.freeze
|
PUBLIC_DISPLAY_BOT_HELP_KEY = "discourse-narrative-bot:track-selector:display-bot-help".freeze
|
||||||
|
|
||||||
TRACKS = [
|
TRACKS = [AdvancedUserNarrative, NewUserNarrative]
|
||||||
AdvancedUserNarrative,
|
|
||||||
NewUserNarrative
|
|
||||||
]
|
|
||||||
|
|
||||||
TOPIC_ACTIONS = [
|
TOPIC_ACTIONS = %i[delete topic_notification_level_changed].each(&:freeze)
|
||||||
:delete,
|
|
||||||
:topic_notification_level_changed
|
|
||||||
].each(&:freeze)
|
|
||||||
|
|
||||||
RESET_TRIGGER_EXACT_MATCH_LENGTH = 200
|
RESET_TRIGGER_EXACT_MATCH_LENGTH = 200
|
||||||
|
|
||||||
|
@ -118,7 +112,7 @@ module DiscourseNarrativeBot
|
||||||
trigger = "#{self.class.reset_trigger} #{klass.reset_trigger}"
|
trigger = "#{self.class.reset_trigger} #{klass.reset_trigger}"
|
||||||
|
|
||||||
if @post.raw.length < RESET_TRIGGER_EXACT_MATCH_LENGTH && @is_pm_to_bot
|
if @post.raw.length < RESET_TRIGGER_EXACT_MATCH_LENGTH && @is_pm_to_bot
|
||||||
@post.raw.match(Regexp.new("\\b\\W\?#{trigger}\\W\?\\b", 'i'))
|
@post.raw.match(Regexp.new("\\b\\W\?#{trigger}\\W\?\\b", "i"))
|
||||||
else
|
else
|
||||||
match_trigger?(trigger)
|
match_trigger?(trigger)
|
||||||
end
|
end
|
||||||
|
@ -127,7 +121,7 @@ module DiscourseNarrativeBot
|
||||||
def bot_commands(hint = true)
|
def bot_commands(hint = true)
|
||||||
raw =
|
raw =
|
||||||
if @user.manually_disabled_discobot?
|
if @user.manually_disabled_discobot?
|
||||||
I18n.t(self.class.i18n_key('random_mention.discobot_disabled'))
|
I18n.t(self.class.i18n_key("random_mention.discobot_disabled"))
|
||||||
elsif match_data = match_trigger?("#{self.class.dice_trigger} (\\d+)d(\\d+)")
|
elsif match_data = match_trigger?("#{self.class.dice_trigger} (\\d+)d(\\d+)")
|
||||||
DiscourseNarrativeBot::Dice.roll(match_data[1].to_i, match_data[2].to_i)
|
DiscourseNarrativeBot::Dice.roll(match_data[1].to_i, match_data[2].to_i)
|
||||||
elsif match_trigger?(self.class.quote_trigger)
|
elsif match_trigger?(self.class.quote_trigger)
|
||||||
|
@ -137,20 +131,23 @@ module DiscourseNarrativeBot
|
||||||
elsif match_trigger?(self.class.help_trigger)
|
elsif match_trigger?(self.class.help_trigger)
|
||||||
help_message
|
help_message
|
||||||
elsif hint
|
elsif hint
|
||||||
message = I18n.t(self.class.i18n_key('random_mention.reply'),
|
message =
|
||||||
discobot_username: self.discobot_username,
|
I18n.t(
|
||||||
help_trigger: self.class.help_trigger
|
self.class.i18n_key("random_mention.reply"),
|
||||||
)
|
discobot_username: self.discobot_username,
|
||||||
|
help_trigger: self.class.help_trigger,
|
||||||
|
)
|
||||||
|
|
||||||
if public_reply?
|
if public_reply?
|
||||||
key = "#{PUBLIC_DISPLAY_BOT_HELP_KEY}:#{@post.topic_id}"
|
key = "#{PUBLIC_DISPLAY_BOT_HELP_KEY}:#{@post.topic_id}"
|
||||||
last_bot_help_post_number = Discourse.redis.get(key)
|
last_bot_help_post_number = Discourse.redis.get(key)
|
||||||
|
|
||||||
if !last_bot_help_post_number ||
|
if !last_bot_help_post_number ||
|
||||||
(last_bot_help_post_number &&
|
(
|
||||||
@post.post_number - 10 > last_bot_help_post_number.to_i &&
|
last_bot_help_post_number &&
|
||||||
(1.day.to_i - Discourse.redis.ttl(key)) > 6.hours.to_i)
|
@post.post_number - 10 > last_bot_help_post_number.to_i &&
|
||||||
|
(1.day.to_i - Discourse.redis.ttl(key)) > 6.hours.to_i
|
||||||
|
)
|
||||||
Discourse.redis.setex(key, 1.day.to_i, @post.post_number)
|
Discourse.redis.setex(key, 1.day.to_i, @post.post_number)
|
||||||
message
|
message
|
||||||
end
|
end
|
||||||
|
@ -166,20 +163,24 @@ module DiscourseNarrativeBot
|
||||||
end
|
end
|
||||||
|
|
||||||
def help_message
|
def help_message
|
||||||
message = I18n.t(
|
message =
|
||||||
self.class.i18n_key('random_mention.tracks'),
|
I18n.t(
|
||||||
discobot_username: self.discobot_username,
|
self.class.i18n_key("random_mention.tracks"),
|
||||||
reset_trigger: self.class.reset_trigger,
|
discobot_username: self.discobot_username,
|
||||||
tracks: [NewUserNarrative.reset_trigger, AdvancedUserNarrative.reset_trigger].join(', ')
|
reset_trigger: self.class.reset_trigger,
|
||||||
)
|
tracks: [NewUserNarrative.reset_trigger, AdvancedUserNarrative.reset_trigger].join(", "),
|
||||||
|
)
|
||||||
|
|
||||||
message << "\n\n#{I18n.t(self.class.i18n_key('random_mention.bot_actions'),
|
message << "\n\n#{
|
||||||
discobot_username: self.discobot_username,
|
I18n.t(
|
||||||
dice_trigger: self.class.dice_trigger,
|
self.class.i18n_key("random_mention.bot_actions"),
|
||||||
quote_trigger: self.class.quote_trigger,
|
discobot_username: self.discobot_username,
|
||||||
quote_sample: DiscourseNarrativeBot::QuoteGenerator.generate(@user),
|
dice_trigger: self.class.dice_trigger,
|
||||||
magic_8_ball_trigger: self.class.magic_8_ball_trigger
|
quote_trigger: self.class.quote_trigger,
|
||||||
)}"
|
quote_sample: DiscourseNarrativeBot::QuoteGenerator.generate(@user),
|
||||||
|
magic_8_ball_trigger: self.class.magic_8_ball_trigger,
|
||||||
|
)
|
||||||
|
}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def generic_replies_key(user)
|
def generic_replies_key(user)
|
||||||
|
@ -193,18 +194,23 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
case count
|
case count
|
||||||
when 0
|
when 0
|
||||||
raw = I18n.t(self.class.i18n_key('do_not_understand.first_response'))
|
raw = I18n.t(self.class.i18n_key("do_not_understand.first_response"))
|
||||||
|
|
||||||
if state && state.to_sym != :end
|
if state && state.to_sym != :end
|
||||||
raw = "#{raw}\n\n#{I18n.t(self.class.i18n_key('do_not_understand.track_response'), reset_trigger: reset_trigger, skip_trigger: self.class.skip_trigger)}"
|
raw =
|
||||||
|
"#{raw}\n\n#{I18n.t(self.class.i18n_key("do_not_understand.track_response"), reset_trigger: reset_trigger, skip_trigger: self.class.skip_trigger)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
reply_to(@post, raw)
|
reply_to(@post, raw)
|
||||||
when 1
|
when 1
|
||||||
reply_to(@post, I18n.t(self.class.i18n_key('do_not_understand.second_response'),
|
reply_to(
|
||||||
base_path: Discourse.base_path,
|
@post,
|
||||||
reset_trigger: self.class.reset_trigger
|
I18n.t(
|
||||||
))
|
self.class.i18n_key("do_not_understand.second_response"),
|
||||||
|
base_path: Discourse.base_path,
|
||||||
|
reset_trigger: self.class.reset_trigger,
|
||||||
|
),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
# Stay out of the user's way
|
# Stay out of the user's way
|
||||||
end
|
end
|
||||||
|
@ -218,7 +224,9 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
def skip_track?
|
def skip_track?
|
||||||
if @is_pm_to_bot
|
if @is_pm_to_bot
|
||||||
@post.raw.match(/((^@#{self.discobot_username} #{self.class.skip_trigger})|(^#{self.class.skip_trigger}$))/i)
|
@post.raw.match(
|
||||||
|
/((^@#{self.discobot_username} #{self.class.skip_trigger})|(^#{self.class.skip_trigger}$))/i,
|
||||||
|
)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -233,24 +241,23 @@ module DiscourseNarrativeBot
|
||||||
def match_trigger?(trigger)
|
def match_trigger?(trigger)
|
||||||
# we remove the leading <p> to allow for trigger to be at the end of a paragraph
|
# we remove the leading <p> to allow for trigger to be at the end of a paragraph
|
||||||
cooked_trigger = cook(trigger)[3..-1]
|
cooked_trigger = cook(trigger)[3..-1]
|
||||||
regexp = Regexp.new(cooked_trigger, 'i')
|
regexp = Regexp.new(cooked_trigger, "i")
|
||||||
match = @post.cooked.match(regexp)
|
match = @post.cooked.match(regexp)
|
||||||
|
|
||||||
if @is_pm_to_bot
|
if @is_pm_to_bot
|
||||||
match || @post.raw.strip.match(Regexp.new("^#{trigger}$", 'i'))
|
match || @post.raw.strip.match(Regexp.new("^#{trigger}$", "i"))
|
||||||
else
|
else
|
||||||
match
|
match
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def like_user_post
|
def like_user_post
|
||||||
if @post.raw.match(/thank/i)
|
PostActionCreator.like(self.discobot_user, @post) if @post.raw.match(/thank/i)
|
||||||
PostActionCreator.like(self.discobot_user, @post)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def bot_mentioned?
|
def bot_mentioned?
|
||||||
@bot_mentioned ||= PostAnalyzer.new(@post.raw, @post.topic_id).raw_mentions.include?(self.discobot_username)
|
@bot_mentioned ||=
|
||||||
|
PostAnalyzer.new(@post.raw, @post.topic_id).raw_mentions.include?(self.discobot_username)
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_reply?
|
def public_reply?
|
||||||
|
|
|
@ -8,8 +8,14 @@ module DiscourseNarrativeBot
|
||||||
|
|
||||||
def self.values
|
def self.values
|
||||||
@values ||= [
|
@values ||= [
|
||||||
{ name: 'discourse_narrative_bot.welcome_post_type.new_user_track', value: 'new_user_track' },
|
{
|
||||||
{ name: 'discourse_narrative_bot.welcome_post_type.welcome_message', value: 'welcome_message' }
|
name: "discourse_narrative_bot.welcome_post_type.new_user_track",
|
||||||
|
value: "new_user_track",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discourse_narrative_bot.welcome_post_type.welcome_message",
|
||||||
|
value: "welcome_message",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,34 +18,37 @@ if Rails.env == "development"
|
||||||
# 3. we have a post_edited hook that queues a job for bot input
|
# 3. we have a post_edited hook that queues a job for bot input
|
||||||
# 4. if you are not running sidekiq in dev every time you save a post it will trigger it
|
# 4. if you are not running sidekiq in dev every time you save a post it will trigger it
|
||||||
# 5. but the constant can not be autoloaded
|
# 5. but the constant can not be autoloaded
|
||||||
Rails.configuration.autoload_paths << File.expand_path('../autoload/jobs', __FILE__)
|
Rails.configuration.autoload_paths << File.expand_path("../autoload/jobs", __FILE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
require_relative 'lib/discourse_narrative_bot/welcome_post_type_site_setting.rb'
|
require_relative "lib/discourse_narrative_bot/welcome_post_type_site_setting.rb"
|
||||||
register_asset 'stylesheets/discourse-narrative-bot.scss'
|
register_asset "stylesheets/discourse-narrative-bot.scss"
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-narrative-bot", "db", "fixtures").to_s
|
SeedFu.fixture_paths << Rails
|
||||||
|
.root
|
||||||
|
.join("plugins", "discourse-narrative-bot", "db", "fixtures")
|
||||||
|
.to_s
|
||||||
|
|
||||||
Mime::Type.register "image/svg+xml", :svg
|
Mime::Type.register "image/svg+xml", :svg
|
||||||
|
|
||||||
[
|
%w[
|
||||||
'../autoload/jobs/regular/bot_input.rb',
|
../autoload/jobs/regular/bot_input.rb
|
||||||
'../autoload/jobs/regular/narrative_timeout.rb',
|
../autoload/jobs/regular/narrative_timeout.rb
|
||||||
'../autoload/jobs/regular/narrative_init.rb',
|
../autoload/jobs/regular/narrative_init.rb
|
||||||
'../autoload/jobs/regular/send_default_welcome_message.rb',
|
../autoload/jobs/regular/send_default_welcome_message.rb
|
||||||
'../autoload/jobs/onceoff/discourse_narrative_bot/grant_badges.rb',
|
../autoload/jobs/onceoff/discourse_narrative_bot/grant_badges.rb
|
||||||
'../autoload/jobs/onceoff/discourse_narrative_bot/remap_old_bot_images.rb',
|
../autoload/jobs/onceoff/discourse_narrative_bot/remap_old_bot_images.rb
|
||||||
'../lib/discourse_narrative_bot/actions.rb',
|
../lib/discourse_narrative_bot/actions.rb
|
||||||
'../lib/discourse_narrative_bot/base.rb',
|
../lib/discourse_narrative_bot/base.rb
|
||||||
'../lib/discourse_narrative_bot/new_user_narrative.rb',
|
../lib/discourse_narrative_bot/new_user_narrative.rb
|
||||||
'../lib/discourse_narrative_bot/advanced_user_narrative.rb',
|
../lib/discourse_narrative_bot/advanced_user_narrative.rb
|
||||||
'../lib/discourse_narrative_bot/track_selector.rb',
|
../lib/discourse_narrative_bot/track_selector.rb
|
||||||
'../lib/discourse_narrative_bot/certificate_generator.rb',
|
../lib/discourse_narrative_bot/certificate_generator.rb
|
||||||
'../lib/discourse_narrative_bot/dice.rb',
|
../lib/discourse_narrative_bot/dice.rb
|
||||||
'../lib/discourse_narrative_bot/quote_generator.rb',
|
../lib/discourse_narrative_bot/quote_generator.rb
|
||||||
'../lib/discourse_narrative_bot/magic_8_ball.rb',
|
../lib/discourse_narrative_bot/magic_8_ball.rb
|
||||||
'../lib/discourse_narrative_bot/welcome_post_type_site_setting.rb'
|
../lib/discourse_narrative_bot/welcome_post_type_site_setting.rb
|
||||||
].each { |path| load File.expand_path(path, __FILE__) }
|
].each { |path| load File.expand_path(path, __FILE__) }
|
||||||
|
|
||||||
RailsMultisite::ConnectionManagement.safe_each_connection do
|
RailsMultisite::ConnectionManagement.safe_each_connection do
|
||||||
|
@ -55,12 +58,13 @@ after_initialize do
|
||||||
|
|
||||||
certificate_path = "#{Discourse.base_url}/discobot/certificate.svg"
|
certificate_path = "#{Discourse.base_url}/discobot/certificate.svg"
|
||||||
if !SiteSetting.allowed_iframes.include?(certificate_path)
|
if !SiteSetting.allowed_iframes.include?(certificate_path)
|
||||||
SiteSetting.allowed_iframes = SiteSetting.allowed_iframes.split('|').append(certificate_path).join('|')
|
SiteSetting.allowed_iframes =
|
||||||
|
SiteSetting.allowed_iframes.split("|").append(certificate_path).join("|")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require_dependency 'plugin_store'
|
require_dependency "plugin_store"
|
||||||
|
|
||||||
module ::DiscourseNarrativeBot
|
module ::DiscourseNarrativeBot
|
||||||
PLUGIN_NAME = "discourse-narrative-bot".freeze
|
PLUGIN_NAME = "discourse-narrative-bot".freeze
|
||||||
|
@ -94,13 +98,15 @@ after_initialize do
|
||||||
immutable_for(24.hours)
|
immutable_for(24.hours)
|
||||||
|
|
||||||
%i[date user_id].each do |key|
|
%i[date user_id].each do |key|
|
||||||
raise Discourse::InvalidParameters.new("#{key} must be present") unless params[key]&.present?
|
unless params[key]&.present?
|
||||||
|
raise Discourse::InvalidParameters.new("#{key} must be present")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:user_id].to_i != current_user.id
|
if params[:user_id].to_i != current_user.id
|
||||||
rate_limiter = RateLimiter.new(current_user, 'svg_certificate', 3, 1.minute)
|
rate_limiter = RateLimiter.new(current_user, "svg_certificate", 3, 1.minute)
|
||||||
else
|
else
|
||||||
rate_limiter = RateLimiter.new(current_user, 'svg_certificate_self', 30, 10.minutes)
|
rate_limiter = RateLimiter.new(current_user, "svg_certificate_self", 30, 10.minutes)
|
||||||
end
|
end
|
||||||
rate_limiter.performed! unless current_user.staff?
|
rate_limiter.performed! unless current_user.staff?
|
||||||
|
|
||||||
|
@ -110,33 +116,28 @@ after_initialize do
|
||||||
hijack do
|
hijack do
|
||||||
generator = CertificateGenerator.new(user, params[:date], avatar_url(user))
|
generator = CertificateGenerator.new(user, params[:date], avatar_url(user))
|
||||||
|
|
||||||
svg = params[:type] == 'advanced' ? generator.advanced_user_track : generator.new_user_track
|
svg =
|
||||||
|
params[:type] == "advanced" ? generator.advanced_user_track : generator.new_user_track
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to { |format| format.svg { render inline: svg } }
|
||||||
format.svg { render inline: svg }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def avatar_url(user)
|
def avatar_url(user)
|
||||||
UrlHelper.absolute(Discourse.base_path + user.avatar_template.gsub('{size}', '250'))
|
UrlHelper.absolute(Discourse.base_path + user.avatar_template.gsub("{size}", "250"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
DiscourseNarrativeBot::Engine.routes.draw do
|
DiscourseNarrativeBot::Engine.routes.draw do
|
||||||
get "/certificate" => "certificates#generate", format: :svg
|
get "/certificate" => "certificates#generate", :format => :svg
|
||||||
end
|
end
|
||||||
|
|
||||||
Discourse::Application.routes.append do
|
Discourse::Application.routes.append { mount ::DiscourseNarrativeBot::Engine, at: "/discobot" }
|
||||||
mount ::DiscourseNarrativeBot::Engine, at: "/discobot"
|
|
||||||
end
|
|
||||||
|
|
||||||
self.add_model_callback(User, :after_destroy) do
|
self.add_model_callback(User, :after_destroy) { DiscourseNarrativeBot::Store.remove(self.id) }
|
||||||
DiscourseNarrativeBot::Store.remove(self.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.on(:user_created) do |user|
|
self.on(:user_created) do |user|
|
||||||
if SiteSetting.discourse_narrative_bot_welcome_post_delay == 0 && !user.staged
|
if SiteSetting.discourse_narrative_bot_welcome_post_delay == 0 && !user.staged
|
||||||
|
@ -145,19 +146,13 @@ after_initialize do
|
||||||
end
|
end
|
||||||
|
|
||||||
self.on(:user_first_logged_in) do |user|
|
self.on(:user_first_logged_in) do |user|
|
||||||
if SiteSetting.discourse_narrative_bot_welcome_post_delay > 0
|
user.enqueue_bot_welcome_post if SiteSetting.discourse_narrative_bot_welcome_post_delay > 0
|
||||||
user.enqueue_bot_welcome_post
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.on(:user_unstaged) do |user|
|
self.on(:user_unstaged) { |user| user.enqueue_bot_welcome_post }
|
||||||
user.enqueue_bot_welcome_post
|
|
||||||
end
|
|
||||||
|
|
||||||
self.add_model_callback(UserOption, :after_save) do
|
self.add_model_callback(UserOption, :after_save) do
|
||||||
if saved_change_to_skip_new_user_tips? && self.skip_new_user_tips
|
user.delete_bot_welcome_post if saved_change_to_skip_new_user_tips? && self.skip_new_user_tips
|
||||||
user.delete_bot_welcome_post
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.add_to_class(:user, :enqueue_bot_welcome_post) do
|
self.add_to_class(:user, :enqueue_bot_welcome_post) do
|
||||||
|
@ -166,28 +161,29 @@ after_initialize do
|
||||||
delay = SiteSetting.discourse_narrative_bot_welcome_post_delay
|
delay = SiteSetting.discourse_narrative_bot_welcome_post_delay
|
||||||
|
|
||||||
case SiteSetting.discourse_narrative_bot_welcome_post_type
|
case SiteSetting.discourse_narrative_bot_welcome_post_type
|
||||||
when 'new_user_track'
|
when "new_user_track"
|
||||||
if enqueue_narrative_bot_job? && !manually_disabled_discobot?
|
if enqueue_narrative_bot_job? && !manually_disabled_discobot?
|
||||||
Jobs.enqueue_in(delay, :narrative_init,
|
Jobs.enqueue_in(
|
||||||
|
delay,
|
||||||
|
:narrative_init,
|
||||||
user_id: self.id,
|
user_id: self.id,
|
||||||
klass: DiscourseNarrativeBot::NewUserNarrative.to_s
|
klass: DiscourseNarrativeBot::NewUserNarrative.to_s,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
when 'welcome_message'
|
when "welcome_message"
|
||||||
Jobs.enqueue_in(delay, :send_default_welcome_message, user_id: self.id)
|
Jobs.enqueue_in(delay, :send_default_welcome_message, user_id: self.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.add_to_class(:user, :manually_disabled_discobot?) do
|
self.add_to_class(:user, :manually_disabled_discobot?) { user_option&.skip_new_user_tips }
|
||||||
user_option&.skip_new_user_tips
|
|
||||||
end
|
|
||||||
|
|
||||||
self.add_to_class(:user, :enqueue_narrative_bot_job?) do
|
self.add_to_class(:user, :enqueue_narrative_bot_job?) do
|
||||||
SiteSetting.discourse_narrative_bot_enabled &&
|
SiteSetting.discourse_narrative_bot_enabled && self.human? && !self.anonymous? &&
|
||||||
self.human? &&
|
|
||||||
!self.anonymous? &&
|
|
||||||
!self.staged &&
|
!self.staged &&
|
||||||
!SiteSetting.discourse_narrative_bot_ignored_usernames.split('|'.freeze).include?(self.username)
|
!SiteSetting
|
||||||
|
.discourse_narrative_bot_ignored_usernames
|
||||||
|
.split("|".freeze)
|
||||||
|
.include?(self.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.add_to_class(:user, :delete_bot_welcome_post) do
|
self.add_to_class(:user, :delete_bot_welcome_post) do
|
||||||
|
@ -219,42 +215,31 @@ after_initialize do
|
||||||
user = post.user
|
user = post.user
|
||||||
|
|
||||||
if user&.enqueue_narrative_bot_job? && !options[:skip_bot]
|
if user&.enqueue_narrative_bot_job? && !options[:skip_bot]
|
||||||
Jobs.enqueue(:bot_input,
|
Jobs.enqueue(:bot_input, user_id: user.id, post_id: post.id, input: "reply")
|
||||||
user_id: user.id,
|
|
||||||
post_id: post.id,
|
|
||||||
input: "reply"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.on(:post_edited) do |post|
|
self.on(:post_edited) do |post|
|
||||||
if post.user&.enqueue_narrative_bot_job?
|
if post.user&.enqueue_narrative_bot_job?
|
||||||
Jobs.enqueue(:bot_input,
|
Jobs.enqueue(:bot_input, user_id: post.user.id, post_id: post.id, input: "edit")
|
||||||
user_id: post.user.id,
|
|
||||||
post_id: post.id,
|
|
||||||
input: "edit"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.on(:post_destroyed) do |post, options, user|
|
self.on(:post_destroyed) do |post, options, user|
|
||||||
if user&.enqueue_narrative_bot_job? && !options[:skip_bot]
|
if user&.enqueue_narrative_bot_job? && !options[:skip_bot]
|
||||||
Jobs.enqueue(:bot_input,
|
Jobs.enqueue(
|
||||||
|
:bot_input,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
topic_id: post.topic_id,
|
topic_id: post.topic_id,
|
||||||
input: "delete"
|
input: "delete",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.on(:post_recovered) do |post, _, user|
|
self.on(:post_recovered) do |post, _, user|
|
||||||
if user&.enqueue_narrative_bot_job?
|
if user&.enqueue_narrative_bot_job?
|
||||||
Jobs.enqueue(:bot_input,
|
Jobs.enqueue(:bot_input, user_id: user.id, post_id: post.id, input: "recover")
|
||||||
user_id: user.id,
|
|
||||||
post_id: post.id,
|
|
||||||
input: "recover"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -268,20 +253,19 @@ after_initialize do
|
||||||
"like"
|
"like"
|
||||||
end
|
end
|
||||||
|
|
||||||
if input
|
Jobs.enqueue(:bot_input, user_id: self.user.id, post_id: self.post.id, input: input) if input
|
||||||
Jobs.enqueue(:bot_input,
|
|
||||||
user_id: self.user.id,
|
|
||||||
post_id: self.post.id,
|
|
||||||
input: input
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.add_model_callback(Bookmark, :after_commit, on: :create) do
|
self.add_model_callback(Bookmark, :after_commit, on: :create) do
|
||||||
if self.user.enqueue_narrative_bot_job?
|
if self.user.enqueue_narrative_bot_job?
|
||||||
if self.bookmarkable_type == "Post"
|
if self.bookmarkable_type == "Post"
|
||||||
Jobs.enqueue(:bot_input, user_id: self.user_id, post_id: self.bookmarkable_id, input: "bookmark")
|
Jobs.enqueue(
|
||||||
|
:bot_input,
|
||||||
|
user_id: self.user_id,
|
||||||
|
post_id: self.bookmarkable_id,
|
||||||
|
input: "bookmark",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -290,31 +274,36 @@ after_initialize do
|
||||||
user = User.find_by(id: user_id)
|
user = User.find_by(id: user_id)
|
||||||
|
|
||||||
if user && user.enqueue_narrative_bot_job?
|
if user && user.enqueue_narrative_bot_job?
|
||||||
Jobs.enqueue(:bot_input,
|
Jobs.enqueue(
|
||||||
|
:bot_input,
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
topic_id: topic_id,
|
topic_id: topic_id,
|
||||||
input: "topic_notification_level_changed"
|
input: "topic_notification_level_changed",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
UserAvatar.register_custom_user_gravatar_email_hash(
|
UserAvatar.register_custom_user_gravatar_email_hash(
|
||||||
DiscourseNarrativeBot::BOT_USER_ID,
|
DiscourseNarrativeBot::BOT_USER_ID,
|
||||||
"discobot@discourse.org"
|
"discobot@discourse.org",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.on(:system_message_sent) do |args|
|
self.on(:system_message_sent) do |args|
|
||||||
next if !SiteSetting.discourse_narrative_bot_enabled
|
next if !SiteSetting.discourse_narrative_bot_enabled
|
||||||
next if args[:message_type] != 'tl2_promotion_message'
|
next if args[:message_type] != "tl2_promotion_message"
|
||||||
|
|
||||||
recipient = args[:post].topic.topic_users.where.not(user_id: args[:post].user_id).last&.user
|
recipient = args[:post].topic.topic_users.where.not(user_id: args[:post].user_id).last&.user
|
||||||
recipient ||= Discourse.site_contact_user if args[:post].user == Discourse.site_contact_user
|
recipient ||= Discourse.site_contact_user if args[:post].user == Discourse.site_contact_user
|
||||||
next if recipient.nil?
|
next if recipient.nil?
|
||||||
|
|
||||||
I18n.with_locale(recipient.effective_locale) do
|
I18n.with_locale(recipient.effective_locale) do
|
||||||
raw = I18n.t("discourse_narrative_bot.tl2_promotion_message.text_body_template",
|
raw =
|
||||||
discobot_username: ::DiscourseNarrativeBot::Base.new.discobot_username,
|
I18n.t(
|
||||||
reset_trigger: "#{::DiscourseNarrativeBot::TrackSelector.reset_trigger} #{::DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger}")
|
"discourse_narrative_bot.tl2_promotion_message.text_body_template",
|
||||||
|
discobot_username: ::DiscourseNarrativeBot::Base.new.discobot_username,
|
||||||
|
reset_trigger:
|
||||||
|
"#{::DiscourseNarrativeBot::TrackSelector.reset_trigger} #{::DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger}",
|
||||||
|
)
|
||||||
|
|
||||||
PostCreator.create!(
|
PostCreator.create!(
|
||||||
::DiscourseNarrativeBot::Base.new.discobot_user,
|
::DiscourseNarrativeBot::Base.new.discobot_user,
|
||||||
|
@ -322,7 +311,7 @@ after_initialize do
|
||||||
raw: raw,
|
raw: raw,
|
||||||
skip_validations: true,
|
skip_validations: true,
|
||||||
archetype: Archetype.private_message,
|
archetype: Archetype.private_message,
|
||||||
target_usernames: recipient.username
|
target_usernames: recipient.username,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -331,12 +320,12 @@ after_initialize do
|
||||||
alias_method :existing_can_create_post?, :can_create_post?
|
alias_method :existing_can_create_post?, :can_create_post?
|
||||||
|
|
||||||
def can_create_post?(parent)
|
def can_create_post?(parent)
|
||||||
return true if SiteSetting.discourse_narrative_bot_enabled &&
|
if SiteSetting.discourse_narrative_bot_enabled && parent.try(:subtype) == "system_message" &&
|
||||||
parent.try(:subtype) == "system_message" &&
|
parent.try(:user) == ::DiscourseNarrativeBot::Base.new.discobot_user
|
||||||
parent.try(:user) == ::DiscourseNarrativeBot::Base.new.discobot_user
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
existing_can_create_post?(parent)
|
existing_can_create_post?(parent)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +1,28 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe DiscourseNarrativeBot::Store do
|
RSpec.describe DiscourseNarrativeBot::Store do
|
||||||
describe '.set' do
|
describe ".set" do
|
||||||
it 'should set the right value in the plugin store' do
|
it "should set the right value in the plugin store" do
|
||||||
key = 'somekey'
|
key = "somekey"
|
||||||
described_class.set(key, 'yay')
|
described_class.set(key, "yay")
|
||||||
plugin_store_row = PluginStoreRow.last
|
plugin_store_row = PluginStoreRow.last
|
||||||
|
|
||||||
expect(plugin_store_row.value).to eq('yay')
|
expect(plugin_store_row.value).to eq("yay")
|
||||||
expect(plugin_store_row.plugin_name).to eq(DiscourseNarrativeBot::PLUGIN_NAME)
|
expect(plugin_store_row.plugin_name).to eq(DiscourseNarrativeBot::PLUGIN_NAME)
|
||||||
expect(plugin_store_row.key).to eq(key)
|
expect(plugin_store_row.key).to eq(key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.get' do
|
describe ".get" do
|
||||||
it 'should get the right value from the plugin store' do
|
it "should get the right value from the plugin store" do
|
||||||
PluginStoreRow.create!(
|
PluginStoreRow.create!(
|
||||||
plugin_name: DiscourseNarrativeBot::PLUGIN_NAME,
|
plugin_name: DiscourseNarrativeBot::PLUGIN_NAME,
|
||||||
key: 'somekey',
|
key: "somekey",
|
||||||
value: 'yay',
|
value: "yay",
|
||||||
type_name: 'string'
|
type_name: "string",
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(described_class.get('somekey')).to eq('yay')
|
expect(described_class.get("somekey")).to eq("yay")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,13 +5,16 @@ RSpec.describe Jobs::DiscourseNarrativeBot::GrantBadges do
|
||||||
let(:other_user) { Fabricate(:user) }
|
let(:other_user) { Fabricate(:user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
DiscourseNarrativeBot::Store.set(user.id, completed: [
|
DiscourseNarrativeBot::Store.set(
|
||||||
DiscourseNarrativeBot::NewUserNarrative.to_s,
|
user.id,
|
||||||
DiscourseNarrativeBot::AdvancedUserNarrative.to_s
|
completed: [
|
||||||
])
|
DiscourseNarrativeBot::NewUserNarrative.to_s,
|
||||||
|
DiscourseNarrativeBot::AdvancedUserNarrative.to_s,
|
||||||
|
],
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should grant the right badges' do
|
it "should grant the right badges" do
|
||||||
described_class.new.execute_onceoff({})
|
described_class.new.execute_onceoff({})
|
||||||
|
|
||||||
expect(user.badges.count).to eq(2)
|
expect(user.badges.count).to eq(2)
|
||||||
|
|
|
@ -3,14 +3,17 @@
|
||||||
RSpec.describe Jobs::DiscourseNarrativeBot::RemapOldBotImages do
|
RSpec.describe Jobs::DiscourseNarrativeBot::RemapOldBotImages do
|
||||||
context "when bot's post contains an old link" do
|
context "when bot's post contains an old link" do
|
||||||
let!(:post) do
|
let!(:post) do
|
||||||
Fabricate(:post,
|
Fabricate(
|
||||||
|
:post,
|
||||||
user: ::DiscourseNarrativeBot::Base.new.discobot_user,
|
user: ::DiscourseNarrativeBot::Base.new.discobot_user,
|
||||||
raw: 'If you’d like to learn more, select <img src="/images/font-awesome-gear.png" width="16" height="16"> <img src="/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!'
|
raw:
|
||||||
|
'If you’d like to learn more, select <img src="/images/font-awesome-gear.png" width="16" height="16"> <img src="/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!',
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should remap the links correctly' do
|
it "should remap the links correctly" do
|
||||||
expected_raw = 'If you’d like to learn more, select <img src="/plugins/discourse-narrative-bot/images/font-awesome-gear.png" width="16" height="16"> <img src="/plugins/discourse-narrative-bot/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/plugins/discourse-narrative-bot/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!'
|
expected_raw =
|
||||||
|
'If you’d like to learn more, select <img src="/plugins/discourse-narrative-bot/images/font-awesome-gear.png" width="16" height="16"> <img src="/plugins/discourse-narrative-bot/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/plugins/discourse-narrative-bot/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!'
|
||||||
|
|
||||||
2.times do
|
2.times do
|
||||||
described_class.new.execute_onceoff({})
|
described_class.new.execute_onceoff({})
|
||||||
|
@ -19,19 +22,21 @@ RSpec.describe Jobs::DiscourseNarrativeBot::RemapOldBotImages do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with subfolder' do
|
context "with subfolder" do
|
||||||
let!(:post) do
|
let!(:post) do
|
||||||
Fabricate(:post,
|
Fabricate(
|
||||||
|
:post,
|
||||||
user: ::DiscourseNarrativeBot::Base.new.discobot_user,
|
user: ::DiscourseNarrativeBot::Base.new.discobot_user,
|
||||||
raw: 'If you’d like to learn more, select <img src="/community/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/community/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!'
|
raw:
|
||||||
|
'If you’d like to learn more, select <img src="/community/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/community/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!',
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should remap the links correctly' do
|
it "should remap the links correctly" do
|
||||||
described_class.new.execute_onceoff({})
|
described_class.new.execute_onceoff({})
|
||||||
|
|
||||||
expect(post.reload.raw).to eq(
|
expect(post.reload.raw).to eq(
|
||||||
'If you’d like to learn more, select <img src="/community/plugins/discourse-narrative-bot/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/community/plugins/discourse-narrative-bot/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!'
|
'If you’d like to learn more, select <img src="/community/plugins/discourse-narrative-bot/images/font-awesome-ellipsis.png" width="16" height="16"> below and <img src="/community/plugins/discourse-narrative-bot/images/font-awesome-bookmark.png" width="16" height="16"> **bookmark this private message**. If you do, there may be a :gift: in your future!',
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,42 +3,51 @@
|
||||||
RSpec.describe Jobs::SendDefaultWelcomeMessage do
|
RSpec.describe Jobs::SendDefaultWelcomeMessage do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
it 'should send the right welcome message' do
|
it "should send the right welcome message" do
|
||||||
described_class.new.execute(user_id: user.id)
|
described_class.new.execute(user_id: user.id)
|
||||||
|
|
||||||
topic = Topic.last
|
topic = Topic.last
|
||||||
|
|
||||||
expect(topic.title).to eq(I18n.t(
|
expect(topic.title).to eq(
|
||||||
"system_messages.welcome_user.subject_template",
|
I18n.t("system_messages.welcome_user.subject_template", site_name: SiteSetting.title),
|
||||||
site_name: SiteSetting.title
|
)
|
||||||
))
|
|
||||||
|
|
||||||
expect(topic.first_post.raw).to eq(I18n.t(
|
expect(topic.first_post.raw).to eq(
|
||||||
"system_messages.welcome_user.text_body_template",
|
I18n.t(
|
||||||
SystemMessage.new(user).defaults
|
"system_messages.welcome_user.text_body_template",
|
||||||
).chomp)
|
SystemMessage.new(user).defaults,
|
||||||
|
).chomp,
|
||||||
|
)
|
||||||
|
|
||||||
expect(topic.closed).to eq(true)
|
expect(topic.closed).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'for an invited user' do
|
describe "for an invited user" do
|
||||||
let(:invite) { Fabricate(:invite, email: 'foo@bar.com') }
|
let(:invite) { Fabricate(:invite, email: "foo@bar.com") }
|
||||||
let(:invited_user) { Fabricate(:invited_user, invite: invite, user: Fabricate(:user, email: 'foo@bar.com'), redeemed_at: Time.zone.now) }
|
let(:invited_user) do
|
||||||
|
Fabricate(
|
||||||
|
:invited_user,
|
||||||
|
invite: invite,
|
||||||
|
user: Fabricate(:user, email: "foo@bar.com"),
|
||||||
|
redeemed_at: Time.zone.now,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it 'should send the right welcome message' do
|
it "should send the right welcome message" do
|
||||||
described_class.new.execute(user_id: invited_user.user_id)
|
described_class.new.execute(user_id: invited_user.user_id)
|
||||||
|
|
||||||
topic = Topic.last
|
topic = Topic.last
|
||||||
|
|
||||||
expect(topic.title).to eq(I18n.t(
|
expect(topic.title).to eq(
|
||||||
"system_messages.welcome_invite.subject_template",
|
I18n.t("system_messages.welcome_invite.subject_template", site_name: SiteSetting.title),
|
||||||
site_name: SiteSetting.title
|
)
|
||||||
))
|
|
||||||
|
|
||||||
expect(topic.first_post.raw).to eq(I18n.t(
|
expect(topic.first_post.raw).to eq(
|
||||||
"system_messages.welcome_invite.text_body_template",
|
I18n.t(
|
||||||
SystemMessage.new(invited_user.user).defaults
|
"system_messages.welcome_invite.text_body_template",
|
||||||
).chomp)
|
SystemMessage.new(invited_user.user).defaults,
|
||||||
|
).chomp,
|
||||||
|
)
|
||||||
|
|
||||||
expect(topic.closed).to eq(true)
|
expect(topic.closed).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,24 +2,21 @@
|
||||||
|
|
||||||
RSpec.describe DiscourseNarrativeBot::CertificateGenerator do
|
RSpec.describe DiscourseNarrativeBot::CertificateGenerator do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:avatar_url) { 'http://test.localhost/cdn/avatar.png' }
|
let(:avatar_url) { "http://test.localhost/cdn/avatar.png" }
|
||||||
let(:date) { "2017-00-10" }
|
let(:date) { "2017-00-10" }
|
||||||
|
|
||||||
describe 'when an invalid date is given' do
|
describe "when an invalid date is given" do
|
||||||
it 'should default to the current date' do
|
it "should default to the current date" do
|
||||||
expect { described_class.new(user, date, avatar_url) }.to_not raise_error
|
expect { described_class.new(user, date, avatar_url) }.to_not raise_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#logo_group' do
|
describe "#logo_group" do
|
||||||
describe 'when SiteSetting.site_logo_small_url is blank' do
|
describe "when SiteSetting.site_logo_small_url is blank" do
|
||||||
before do
|
before { SiteSetting.logo_small = "" }
|
||||||
SiteSetting.logo_small = ''
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should not try to fetch a image' do
|
it "should not try to fetch a image" do
|
||||||
expect(described_class.new(user, date, avatar_url).send(:logo_group, 1, 1, 1))
|
expect(described_class.new(user, date, avatar_url).send(:logo_group, 1, 1, 1)).to eq(nil)
|
||||||
.to eq(nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,48 +1,43 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe "Discobot Certificate" do
|
RSpec.describe "Discobot Certificate" do
|
||||||
let(:user) { Fabricate(:user, name: 'Jeff Atwood') }
|
let(:user) { Fabricate(:user, name: "Jeff Atwood") }
|
||||||
|
|
||||||
let(:params) {
|
let(:params) { { date: Time.zone.now.strftime("%b %d %Y"), user_id: user.id } }
|
||||||
{
|
|
||||||
date: Time.zone.now.strftime("%b %d %Y"),
|
|
||||||
user_id: user.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe 'when viewing the certificate' do
|
describe "when viewing the certificate" do
|
||||||
describe 'when no logged in' do
|
describe "when no logged in" do
|
||||||
it 'should return the right response' do
|
it "should return the right response" do
|
||||||
get '/discobot/certificate.svg', params: params
|
get "/discobot/certificate.svg", params: params
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when logged in' do
|
describe "when logged in" do
|
||||||
before do
|
before { sign_in(user) }
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should return the right text' do
|
it "should return the right text" do
|
||||||
stub_request(:get, /letter_avatar_proxy/).to_return(status: 200, body: 'http://test.localhost/cdn/avatar.png')
|
stub_request(:get, /letter_avatar_proxy/).to_return(
|
||||||
|
status: 200,
|
||||||
|
body: "http://test.localhost/cdn/avatar.png",
|
||||||
|
)
|
||||||
stub_request(:get, /avatar.png/).to_return(status: 200)
|
stub_request(:get, /avatar.png/).to_return(status: 200)
|
||||||
|
|
||||||
stub_request(:get, SiteSetting.site_logo_small_url)
|
stub_request(:get, SiteSetting.site_logo_small_url).to_return(status: 200)
|
||||||
.to_return(status: 200)
|
|
||||||
|
|
||||||
get '/discobot/certificate.svg', params: params
|
get "/discobot/certificate.svg", params: params
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.body).to include('<svg')
|
expect(response.body).to include("<svg")
|
||||||
expect(response.body).to include(user.avatar_template.gsub('{size}', '250'))
|
expect(response.body).to include(user.avatar_template.gsub("{size}", "250"))
|
||||||
expect(response.body).to include(SiteSetting.site_logo_small_url)
|
expect(response.body).to include(SiteSetting.site_logo_small_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when params are missing' do
|
describe "when params are missing" do
|
||||||
it "should raise the right errors" do
|
it "should raise the right errors" do
|
||||||
params.each do |key, _|
|
params.each do |key, _|
|
||||||
get '/discobot/certificate.svg', params: params.except(key)
|
get "/discobot/certificate.svg", params: params.except(key)
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,40 +3,39 @@
|
||||||
RSpec.describe "Discobot welcome post" do
|
RSpec.describe "Discobot welcome post" do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
before do
|
before { SiteSetting.discourse_narrative_bot_enabled = true }
|
||||||
SiteSetting.discourse_narrative_bot_enabled = true
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when discourse_narrative_bot_welcome_post_delay is 0' do
|
context "when discourse_narrative_bot_welcome_post_delay is 0" do
|
||||||
it 'should not delay the welcome post' do
|
it "should not delay the welcome post" do
|
||||||
user
|
user
|
||||||
expect { sign_in(user) }.to_not change { Jobs::NarrativeInit.jobs.count }
|
expect { sign_in(user) }.to_not change { Jobs::NarrativeInit.jobs.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when discourse_narrative_bot_welcome_post_delay is greater than 0' do
|
context "when discourse_narrative_bot_welcome_post_delay is greater than 0" do
|
||||||
before do
|
before { SiteSetting.discourse_narrative_bot_welcome_post_delay = 5 }
|
||||||
SiteSetting.discourse_narrative_bot_welcome_post_delay = 5
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user logs in normally' do
|
context "when user logs in normally" do
|
||||||
it 'should delay the welcome post until user logs in' do
|
it "should delay the welcome post until user logs in" do
|
||||||
expect { sign_in(user) }.to change { Jobs::NarrativeInit.jobs.count }.by(1)
|
expect { sign_in(user) }.to change { Jobs::NarrativeInit.jobs.count }.by(1)
|
||||||
expect(Jobs::NarrativeInit.jobs.first["args"].first["user_id"]).to eq(user.id)
|
expect(Jobs::NarrativeInit.jobs.first["args"].first["user_id"]).to eq(user.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user redeems an invite' do
|
context "when user redeems an invite" do
|
||||||
let!(:invite) { Fabricate(:invite, invited_by: Fabricate(:admin), email: 'testing@gmail.com') }
|
let!(:invite) do
|
||||||
|
Fabricate(:invite, invited_by: Fabricate(:admin), email: "testing@gmail.com")
|
||||||
|
end
|
||||||
|
|
||||||
it 'should delay the welcome post until the user logs in' do
|
it "should delay the welcome post until the user logs in" do
|
||||||
expect do
|
expect do
|
||||||
put "/invites/show/#{invite.invite_key}.json", params: {
|
put "/invites/show/#{invite.invite_key}.json",
|
||||||
username: 'somename',
|
params: {
|
||||||
name: 'testing',
|
username: "somename",
|
||||||
password: 'verystrongpassword',
|
name: "testing",
|
||||||
email_token: invite.email_token
|
password: "verystrongpassword",
|
||||||
}
|
email_token: invite.email_token,
|
||||||
|
}
|
||||||
end.to change { User.count }.by(1)
|
end.to change { User.count }.by(1)
|
||||||
|
|
||||||
expect(Jobs::NarrativeInit.jobs.first["args"].first["user_id"]).to eq(User.last.id)
|
expect(Jobs::NarrativeInit.jobs.first["args"].first["user_id"]).to eq(User.last.id)
|
||||||
|
@ -44,14 +43,12 @@ RSpec.describe "Discobot welcome post" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is staged' do
|
context "when user is staged" do
|
||||||
let(:staged_user) { Fabricate(:user, staged: true) }
|
let(:staged_user) { Fabricate(:user, staged: true) }
|
||||||
|
|
||||||
before do
|
before { SiteSetting.discourse_narrative_bot_welcome_post_type = "welcome_message" }
|
||||||
SiteSetting.discourse_narrative_bot_welcome_post_type = 'welcome_message'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should not send welcome message' do
|
it "should not send welcome message" do
|
||||||
expect { staged_user }.to_not change { Jobs::SendDefaultWelcomeMessage.jobs.count }
|
expect { staged_user }.to_not change { Jobs::SendDefaultWelcomeMessage.jobs.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,79 +18,76 @@ RSpec.describe User do
|
||||||
SiteSetting.discourse_narrative_bot_enabled = true
|
SiteSetting.discourse_narrative_bot_enabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when a user is created' do
|
describe "when a user is created" do
|
||||||
it 'should initiate the bot' do
|
it "should initiate the bot" do
|
||||||
NotificationEmailer.expects(:process_notification).never
|
NotificationEmailer.expects(:process_notification).never
|
||||||
|
|
||||||
user
|
user
|
||||||
|
|
||||||
expected_raw = i18n_t('discourse_narrative_bot.new_user_narrative.hello.message',
|
expected_raw =
|
||||||
username: user.username, title: SiteSetting.title
|
i18n_t(
|
||||||
)
|
"discourse_narrative_bot.new_user_narrative.hello.message",
|
||||||
|
username: user.username,
|
||||||
|
title: SiteSetting.title,
|
||||||
|
)
|
||||||
|
|
||||||
expect(Post.last.raw).to include(expected_raw.chomp)
|
expect(Post.last.raw).to include(expected_raw.chomp)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'welcome post' do
|
describe "welcome post" do
|
||||||
context 'when disabled' do
|
context "when disabled" do
|
||||||
before do
|
before { SiteSetting.disable_discourse_narrative_bot_welcome_post = true }
|
||||||
SiteSetting.disable_discourse_narrative_bot_welcome_post = true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should not initiate the bot' do
|
it "should not initiate the bot" do
|
||||||
expect { user }.to_not change { Post.count }
|
expect { user }.to_not change { Post.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with title emoji disabled' do
|
context "with title emoji disabled" do
|
||||||
before do
|
before do
|
||||||
SiteSetting.disable_discourse_narrative_bot_welcome_post = false
|
SiteSetting.disable_discourse_narrative_bot_welcome_post = false
|
||||||
SiteSetting.max_emojis_in_title = 0
|
SiteSetting.max_emojis_in_title = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'initiates the bot' do
|
it "initiates the bot" do
|
||||||
expect { user }.to change { Topic.count }.by(1)
|
expect { user }.to change { Topic.count }.by(1)
|
||||||
|
|
||||||
expect(Topic.last.title).to eq(i18n_t(
|
expect(Topic.last.title).to eq(
|
||||||
'discourse_narrative_bot.new_user_narrative.hello.title'
|
i18n_t("discourse_narrative_bot.new_user_narrative.hello.title").gsub(
|
||||||
).gsub(/:robot:/, '').strip)
|
/:robot:/,
|
||||||
|
"",
|
||||||
|
).strip,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when enabled' do
|
context "when enabled" do
|
||||||
before do
|
before { SiteSetting.disable_discourse_narrative_bot_welcome_post = false }
|
||||||
SiteSetting.disable_discourse_narrative_bot_welcome_post = false
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'initiate the bot' do
|
it "initiate the bot" do
|
||||||
expect { user }.to change { Topic.count }.by(1)
|
expect { user }.to change { Topic.count }.by(1)
|
||||||
|
|
||||||
expect(Topic.last.title).to eq(i18n_t(
|
expect(Topic.last.title).to eq(
|
||||||
'discourse_narrative_bot.new_user_narrative.hello.title'
|
i18n_t("discourse_narrative_bot.new_user_narrative.hello.title"),
|
||||||
))
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "when send welcome message is selected" do
|
describe "when send welcome message is selected" do
|
||||||
before do
|
before { SiteSetting.discourse_narrative_bot_welcome_post_type = "welcome_message" }
|
||||||
SiteSetting.discourse_narrative_bot_welcome_post_type = 'welcome_message'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should send the right welcome message' do
|
it "should send the right welcome message" do
|
||||||
expect { user }.to change { Topic.count }.by(1)
|
expect { user }.to change { Topic.count }.by(1)
|
||||||
|
|
||||||
expect(Topic.last.title).to eq(i18n_t(
|
expect(Topic.last.title).to eq(
|
||||||
"system_messages.welcome_user.subject_template",
|
i18n_t("system_messages.welcome_user.subject_template", site_name: SiteSetting.title),
|
||||||
site_name: SiteSetting.title
|
)
|
||||||
))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when welcome message is configured to be delayed' do
|
describe "when welcome message is configured to be delayed" do
|
||||||
before do
|
before { SiteSetting.discourse_narrative_bot_welcome_post_delay = 100 }
|
||||||
SiteSetting.discourse_narrative_bot_welcome_post_delay = 100
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should delay the welcome post until user logs in' do
|
it "should delay the welcome post until user logs in" do
|
||||||
user
|
user
|
||||||
|
|
||||||
expect(Jobs::NarrativeInit.jobs.count).to eq(0)
|
expect(Jobs::NarrativeInit.jobs.count).to eq(0)
|
||||||
|
@ -99,41 +96,37 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is staged' do
|
context "when user is staged" do
|
||||||
let(:user) { Fabricate(:user, staged: true) }
|
let(:user) { Fabricate(:user, staged: true) }
|
||||||
|
|
||||||
it 'should not initiate the bot' do
|
it "should not initiate the bot" do
|
||||||
expect { user }.to_not change { Post.count }
|
expect { user }.to_not change { Post.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user skipped the new user tips' do
|
context "when user skipped the new user tips" do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
it 'should not initiate the bot' do
|
it "should not initiate the bot" do
|
||||||
SiteSetting.default_other_skip_new_user_tips = true
|
SiteSetting.default_other_skip_new_user_tips = true
|
||||||
expect { user }.to_not change { Post.count }
|
expect { user }.to_not change { Post.count }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should delete the existing PM' do
|
it "should delete the existing PM" do
|
||||||
user.user_option.skip_new_user_tips = true
|
user.user_option.skip_new_user_tips = true
|
||||||
|
|
||||||
expect {
|
expect { user.user_option.save! }.to change { Topic.count }.by(-1).and not_change {
|
||||||
user.user_option.save!
|
UserHistory.count
|
||||||
}.to change { Topic.count }.by(-1)
|
}.and change { user.unread_high_priority_notifications }.by(-1).and change {
|
||||||
.and not_change { UserHistory.count }
|
user.notifications.count
|
||||||
.and change { user.unread_high_priority_notifications }.by(-1)
|
}.by(-1)
|
||||||
.and change { user.notifications.count }.by(-1)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is anonymous?' do
|
context "when user is anonymous?" do
|
||||||
before do
|
before { SiteSetting.allow_anonymous_posting = true }
|
||||||
SiteSetting.allow_anonymous_posting = true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should initiate bot for real user only' do
|
|
||||||
|
|
||||||
|
it "should initiate bot for real user only" do
|
||||||
user = Fabricate(:user, trust_level: 1)
|
user = Fabricate(:user, trust_level: 1)
|
||||||
shadow = AnonymousShadowCreator.get(user)
|
shadow = AnonymousShadowCreator.get(user)
|
||||||
|
|
||||||
|
@ -145,21 +138,19 @@ RSpec.describe User do
|
||||||
context "when user's username should be ignored" do
|
context "when user's username should be ignored" do
|
||||||
let(:user) { Fabricate.build(:user) }
|
let(:user) { Fabricate.build(:user) }
|
||||||
|
|
||||||
before do
|
before { SiteSetting.discourse_narrative_bot_ignored_usernames = "discourse|test" }
|
||||||
SiteSetting.discourse_narrative_bot_ignored_usernames = 'discourse|test'
|
|
||||||
end
|
|
||||||
|
|
||||||
['discourse', 'test'].each do |username|
|
%w[discourse test].each do |username|
|
||||||
it 'should not initiate the bot' do
|
it "should not initiate the bot" do
|
||||||
expect { user.update!(username: username) }.to_not change { Post.count }
|
expect { user.update!(username: username) }.to_not change { Post.count }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when a user has been destroyed' do
|
describe "when a user has been destroyed" do
|
||||||
it "should clean up plugin's store" do
|
it "should clean up plugin's store" do
|
||||||
DiscourseNarrativeBot::Store.set(user.id, 'test')
|
DiscourseNarrativeBot::Store.set(user.id, "test")
|
||||||
|
|
||||||
user.destroy!
|
user.destroy!
|
||||||
|
|
||||||
|
@ -167,8 +158,8 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#manually_disabled_discobot?' do
|
describe "#manually_disabled_discobot?" do
|
||||||
it 'returns true if the user manually disabled new user tips' do
|
it "returns true if the user manually disabled new user tips" do
|
||||||
user.user_option.skip_new_user_tips = true
|
user.user_option.skip_new_user_tips = true
|
||||||
|
|
||||||
expect(user.manually_disabled_discobot?).to eq(true)
|
expect(user.manually_disabled_discobot?).to eq(true)
|
||||||
|
|
|
@ -9,40 +9,40 @@
|
||||||
enabled_site_setting :presence_enabled
|
enabled_site_setting :presence_enabled
|
||||||
hide_plugin if self.respond_to?(:hide_plugin)
|
hide_plugin if self.respond_to?(:hide_plugin)
|
||||||
|
|
||||||
register_asset 'stylesheets/presence.scss'
|
register_asset "stylesheets/presence.scss"
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
|
||||||
register_presence_channel_prefix("discourse-presence") do |channel_name|
|
register_presence_channel_prefix("discourse-presence") do |channel_name|
|
||||||
if topic_id = channel_name[/\/discourse-presence\/reply\/(\d+)/, 1]
|
if topic_id = channel_name[%r{/discourse-presence/reply/(\d+)}, 1]
|
||||||
topic = Topic.find(topic_id)
|
topic = Topic.find(topic_id)
|
||||||
config = PresenceChannel::Config.new
|
config = PresenceChannel::Config.new
|
||||||
|
|
||||||
if topic.private_message?
|
if topic.private_message?
|
||||||
config.allowed_user_ids = topic.allowed_users.pluck(:id)
|
config.allowed_user_ids = topic.allowed_users.pluck(:id)
|
||||||
config.allowed_group_ids = topic.allowed_groups.pluck(:group_id) + [::Group::AUTO_GROUPS[:staff]]
|
config.allowed_group_ids =
|
||||||
|
topic.allowed_groups.pluck(:group_id) + [::Group::AUTO_GROUPS[:staff]]
|
||||||
elsif secure_group_ids = topic.secure_group_ids
|
elsif secure_group_ids = topic.secure_group_ids
|
||||||
config.allowed_group_ids = secure_group_ids + [::Group::AUTO_GROUPS[:admins]]
|
config.allowed_group_ids = secure_group_ids + [::Group::AUTO_GROUPS[:admins]]
|
||||||
else
|
else
|
||||||
# config.public=true would make data available to anon, so use the tl0 group instead
|
# config.public=true would make data available to anon, so use the tl0 group instead
|
||||||
config.allowed_group_ids = [ ::Group::AUTO_GROUPS[:trust_level_0] ]
|
config.allowed_group_ids = [::Group::AUTO_GROUPS[:trust_level_0]]
|
||||||
end
|
end
|
||||||
|
|
||||||
config
|
config
|
||||||
elsif topic_id = channel_name[/\/discourse-presence\/whisper\/(\d+)/, 1]
|
elsif topic_id = channel_name[%r{/discourse-presence/whisper/(\d+)}, 1]
|
||||||
Topic.find(topic_id) # Just ensure it exists
|
Topic.find(topic_id) # Just ensure it exists
|
||||||
PresenceChannel::Config.new(allowed_group_ids: [::Group::AUTO_GROUPS[:staff]])
|
PresenceChannel::Config.new(allowed_group_ids: [::Group::AUTO_GROUPS[:staff]])
|
||||||
elsif post_id = channel_name[/\/discourse-presence\/edit\/(\d+)/, 1]
|
elsif post_id = channel_name[%r{/discourse-presence/edit/(\d+)}, 1]
|
||||||
post = Post.find(post_id)
|
post = Post.find(post_id)
|
||||||
topic = Topic.find(post.topic_id)
|
topic = Topic.find(post.topic_id)
|
||||||
|
|
||||||
config = PresenceChannel::Config.new
|
config = PresenceChannel::Config.new
|
||||||
config.allowed_group_ids = [ ::Group::AUTO_GROUPS[:staff] ]
|
config.allowed_group_ids = [::Group::AUTO_GROUPS[:staff]]
|
||||||
|
|
||||||
# Locked and whisper posts are staff only
|
# Locked and whisper posts are staff only
|
||||||
next config if post.locked? || post.whisper?
|
next config if post.locked? || post.whisper?
|
||||||
|
|
||||||
config.allowed_user_ids = [ post.user_id ]
|
config.allowed_user_ids = [post.user_id]
|
||||||
|
|
||||||
if topic.private_message? && post.wiki
|
if topic.private_message? && post.wiki
|
||||||
# Ignore trust level and just publish to all allowed groups since
|
# Ignore trust level and just publish to all allowed groups since
|
||||||
|
@ -52,14 +52,17 @@ after_initialize do
|
||||||
config.allowed_user_ids += topic.allowed_users.pluck(:id)
|
config.allowed_user_ids += topic.allowed_users.pluck(:id)
|
||||||
config.allowed_group_ids += topic.allowed_groups.pluck(:id)
|
config.allowed_group_ids += topic.allowed_groups.pluck(:id)
|
||||||
elsif post.wiki
|
elsif post.wiki
|
||||||
config.allowed_group_ids << Group::AUTO_GROUPS[:"trust_level_#{SiteSetting.min_trust_to_edit_wiki_post}"]
|
config.allowed_group_ids << Group::AUTO_GROUPS[
|
||||||
|
:"trust_level_#{SiteSetting.min_trust_to_edit_wiki_post}"
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
if !topic.private_message? && SiteSetting.trusted_users_can_edit_others?
|
if !topic.private_message? && SiteSetting.trusted_users_can_edit_others?
|
||||||
config.allowed_group_ids << Group::AUTO_GROUPS[:trust_level_4]
|
config.allowed_group_ids << Group::AUTO_GROUPS[:trust_level_4]
|
||||||
end
|
end
|
||||||
|
|
||||||
if SiteSetting.enable_category_group_moderation? && group_id = topic.category&.reviewable_by_group_id
|
if SiteSetting.enable_category_group_moderation? &&
|
||||||
|
group_id = topic.category&.reviewable_by_group_id
|
||||||
config.allowed_group_ids << group_id
|
config.allowed_group_ids << group_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe "discourse-presence" do
|
RSpec.describe "discourse-presence" do
|
||||||
describe 'PresenceChannel configuration' do
|
describe "PresenceChannel configuration" do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
fab!(:user2) { Fabricate(:user) }
|
fab!(:user2) { Fabricate(:user) }
|
||||||
fab!(:admin) { Fabricate(:admin) }
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
@ -16,25 +16,21 @@ RSpec.describe "discourse-presence" do
|
||||||
fab!(:private_topic) { Fabricate(:topic, category: category) }
|
fab!(:private_topic) { Fabricate(:topic, category: category) }
|
||||||
fab!(:public_topic) { Fabricate(:topic, first_post: Fabricate(:post)) }
|
fab!(:public_topic) { Fabricate(:topic, first_post: Fabricate(:post)) }
|
||||||
|
|
||||||
fab!(:private_message) do
|
fab!(:private_message) { Fabricate(:private_message_topic, allowed_groups: [group]) }
|
||||||
Fabricate(:private_message_topic,
|
|
||||||
allowed_groups: [group]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
before { PresenceChannel.clear_all! }
|
before { PresenceChannel.clear_all! }
|
||||||
|
|
||||||
it 'handles invalid topic IDs' do
|
it "handles invalid topic IDs" do
|
||||||
expect do
|
expect do PresenceChannel.new("/discourse-presence/reply/-999").config end.to raise_error(
|
||||||
PresenceChannel.new('/discourse-presence/reply/-999').config
|
PresenceChannel::NotFound,
|
||||||
end.to raise_error(PresenceChannel::NotFound)
|
)
|
||||||
|
|
||||||
expect do
|
expect do PresenceChannel.new("/discourse-presence/reply/blah").config end.to raise_error(
|
||||||
PresenceChannel.new('/discourse-presence/reply/blah').config
|
PresenceChannel::NotFound,
|
||||||
end.to raise_error(PresenceChannel::NotFound)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles deleted topics' do
|
it "handles deleted topics" do
|
||||||
public_topic.trash!
|
public_topic.trash!
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
|
@ -50,7 +46,7 @@ RSpec.describe "discourse-presence" do
|
||||||
end.to raise_error(PresenceChannel::NotFound)
|
end.to raise_error(PresenceChannel::NotFound)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles secure category permissions for reply' do
|
it "handles secure category permissions for reply" do
|
||||||
c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}")
|
c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}")
|
||||||
expect(c.can_view?(user_id: user.id)).to eq(true)
|
expect(c.can_view?(user_id: user.id)).to eq(true)
|
||||||
expect(c.can_enter?(user_id: user.id)).to eq(true)
|
expect(c.can_enter?(user_id: user.id)).to eq(true)
|
||||||
|
@ -62,14 +58,14 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.can_enter?(user_id: user.id)).to eq(false)
|
expect(c.can_enter?(user_id: user.id)).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles secure category permissions for edit' do
|
it "handles secure category permissions for edit" do
|
||||||
p = Fabricate(:post, topic: private_topic, user: private_topic.user)
|
p = Fabricate(:post, topic: private_topic, user: private_topic.user)
|
||||||
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
||||||
expect(c.can_view?(user_id: user.id)).to eq(false)
|
expect(c.can_view?(user_id: user.id)).to eq(false)
|
||||||
expect(c.can_view?(user_id: private_topic.user.id)).to eq(true)
|
expect(c.can_view?(user_id: private_topic.user.id)).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles category moderators for edit' do
|
it "handles category moderators for edit" do
|
||||||
SiteSetting.trusted_users_can_edit_others = false
|
SiteSetting.trusted_users_can_edit_others = false
|
||||||
p = Fabricate(:post, topic: private_topic, user: private_topic.user)
|
p = Fabricate(:post, topic: private_topic, user: private_topic.user)
|
||||||
|
|
||||||
|
@ -83,25 +79,25 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff], group.id)
|
expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff], group.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles permissions for a public topic' do
|
it "handles permissions for a public topic" do
|
||||||
c = PresenceChannel.new("/discourse-presence/reply/#{public_topic.id}")
|
c = PresenceChannel.new("/discourse-presence/reply/#{public_topic.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(::Group::AUTO_GROUPS[:trust_level_0])
|
expect(c.config.allowed_group_ids).to contain_exactly(::Group::AUTO_GROUPS[:trust_level_0])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles permissions for secure category topics' do
|
it "handles permissions for secure category topics" do
|
||||||
c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}")
|
c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(group.id, Group::AUTO_GROUPS[:admins])
|
expect(c.config.allowed_group_ids).to contain_exactly(group.id, Group::AUTO_GROUPS[:admins])
|
||||||
expect(c.config.allowed_user_ids).to eq(nil)
|
expect(c.config.allowed_user_ids).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles permissions for private messages' do
|
it "handles permissions for private messages" do
|
||||||
c = PresenceChannel.new("/discourse-presence/reply/#{private_message.id}")
|
c = PresenceChannel.new("/discourse-presence/reply/#{private_message.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(group.id, Group::AUTO_GROUPS[:staff])
|
expect(c.config.allowed_group_ids).to contain_exactly(group.id, Group::AUTO_GROUPS[:staff])
|
||||||
expect(c.config.allowed_user_ids).to contain_exactly(
|
expect(c.config.allowed_user_ids).to contain_exactly(
|
||||||
*private_message.topic_allowed_users.pluck(:user_id)
|
*private_message.topic_allowed_users.pluck(:user_id),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -112,7 +108,7 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.config.allowed_user_ids).to eq(nil)
|
expect(c.config.allowed_user_ids).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'only allows staff when editing whispers' do
|
it "only allows staff when editing whispers" do
|
||||||
p = Fabricate(:whisper, topic: public_topic, user: admin)
|
p = Fabricate(:whisper, topic: public_topic, user: admin)
|
||||||
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
|
@ -120,7 +116,7 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.config.allowed_user_ids).to eq(nil)
|
expect(c.config.allowed_user_ids).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'only allows staff when editing a locked post' do
|
it "only allows staff when editing a locked post" do
|
||||||
p = Fabricate(:post, topic: public_topic, user: admin, locked_by_id: Discourse.system_user.id)
|
p = Fabricate(:post, topic: public_topic, user: admin, locked_by_id: Discourse.system_user.id)
|
||||||
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
|
@ -134,7 +130,7 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(
|
expect(c.config.allowed_group_ids).to contain_exactly(
|
||||||
Group::AUTO_GROUPS[:trust_level_4],
|
Group::AUTO_GROUPS[:trust_level_4],
|
||||||
Group::AUTO_GROUPS[:staff]
|
Group::AUTO_GROUPS[:staff],
|
||||||
)
|
)
|
||||||
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
||||||
end
|
end
|
||||||
|
@ -145,9 +141,7 @@ RSpec.describe "discourse-presence" do
|
||||||
p = Fabricate(:post, topic: public_topic, user: user)
|
p = Fabricate(:post, topic: public_topic, user: user)
|
||||||
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(
|
expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
|
||||||
Group::AUTO_GROUPS[:staff]
|
|
||||||
)
|
|
||||||
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -160,7 +154,7 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(
|
expect(c.config.allowed_group_ids).to contain_exactly(
|
||||||
Group::AUTO_GROUPS[:staff],
|
Group::AUTO_GROUPS[:staff],
|
||||||
Group::AUTO_GROUPS[:trust_level_1]
|
Group::AUTO_GROUPS[:trust_level_1],
|
||||||
)
|
)
|
||||||
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
||||||
end
|
end
|
||||||
|
@ -170,9 +164,7 @@ RSpec.describe "discourse-presence" do
|
||||||
|
|
||||||
c = PresenceChannel.new("/discourse-presence/edit/#{post.id}")
|
c = PresenceChannel.new("/discourse-presence/edit/#{post.id}")
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(
|
expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
|
||||||
Group::AUTO_GROUPS[:staff]
|
|
||||||
)
|
|
||||||
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
expect(c.config.allowed_user_ids).to contain_exactly(user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -183,9 +175,12 @@ RSpec.describe "discourse-presence" do
|
||||||
expect(c.config.public).to eq(false)
|
expect(c.config.public).to eq(false)
|
||||||
expect(c.config.allowed_group_ids).to contain_exactly(
|
expect(c.config.allowed_group_ids).to contain_exactly(
|
||||||
Group::AUTO_GROUPS[:staff],
|
Group::AUTO_GROUPS[:staff],
|
||||||
*private_message.allowed_groups.pluck(:id)
|
*private_message.allowed_groups.pluck(:id),
|
||||||
|
)
|
||||||
|
expect(c.config.allowed_user_ids).to contain_exactly(
|
||||||
|
user.id,
|
||||||
|
*private_message.allowed_users.pluck(:id),
|
||||||
)
|
)
|
||||||
expect(c.config.allowed_user_ids).to contain_exactly(user.id, *private_message.allowed_users.pluck(:id))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,11 +21,10 @@ class Onebox::Engine::YoutubeOnebox
|
||||||
alias_method :yt_onebox_to_html, :to_html
|
alias_method :yt_onebox_to_html, :to_html
|
||||||
|
|
||||||
def to_html
|
def to_html
|
||||||
if video_id && !params['list']
|
if video_id && !params["list"]
|
||||||
|
size_restricted = [params["width"], params["height"]].any?
|
||||||
size_restricted = [params['width'], params['height']].any?
|
video_width = (params["width"] && params["width"].to_i <= 695) ? params["width"] : 690 # embed width
|
||||||
video_width = (params['width'] && params['width'].to_i <= 695) ? params['width'] : 690 # embed width
|
video_height = (params["height"] && params["height"].to_i <= 500) ? params["height"] : 388 # embed height
|
||||||
video_height = (params['height'] && params['height'].to_i <= 500) ? params['height'] : 388 # embed height
|
|
||||||
size_tags = ["width=\"#{video_width}\"", "height=\"#{video_height}\""]
|
size_tags = ["width=\"#{video_width}\"", "height=\"#{video_height}\""]
|
||||||
|
|
||||||
result = parse_embed_response
|
result = parse_embed_response
|
||||||
|
@ -40,12 +39,12 @@ class Onebox::Engine::YoutubeOnebox
|
||||||
<div class="onebox lazyYT lazyYT-container"
|
<div class="onebox lazyYT lazyYT-container"
|
||||||
data-youtube-id="#{video_id}"
|
data-youtube-id="#{video_id}"
|
||||||
data-youtube-title="#{escaped_title}"
|
data-youtube-title="#{escaped_title}"
|
||||||
#{size_restricted ? size_tags.map { |t| "data-#{t}" }.join(' ') : ""}
|
#{size_restricted ? size_tags.map { |t| "data-#{t}" }.join(" ") : ""}
|
||||||
data-parameters="#{embed_params}">
|
data-parameters="#{embed_params}">
|
||||||
<a href="https://www.youtube.com/watch?v=#{video_id}" target="_blank">
|
<a href="https://www.youtube.com/watch?v=#{video_id}" target="_blank">
|
||||||
<img class="ytp-thumbnail-image"
|
<img class="ytp-thumbnail-image"
|
||||||
src="#{thumbnail_url}"
|
src="#{thumbnail_url}"
|
||||||
#{size_restricted ? size_tags.join(' ') : ""}
|
#{size_restricted ? size_tags.join(" ") : ""}
|
||||||
title="#{escaped_title}">
|
title="#{escaped_title}">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,23 +53,22 @@ class Onebox::Engine::YoutubeOnebox
|
||||||
yt_onebox_to_html
|
yt_onebox_to_html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
|
||||||
on(:reduce_cooked) do |fragment|
|
on(:reduce_cooked) do |fragment|
|
||||||
fragment.css(".lazyYT").each do |yt|
|
fragment
|
||||||
begin
|
.css(".lazyYT")
|
||||||
youtube_id = yt["data-youtube-id"]
|
.each do |yt|
|
||||||
parameters = yt["data-parameters"]
|
begin
|
||||||
uri = URI("https://www.youtube.com/embed/#{youtube_id}?autoplay=1&#{parameters}")
|
youtube_id = yt["data-youtube-id"]
|
||||||
yt.replace %{<p><a href="#{uri.to_s}">https://#{uri.host}#{uri.path}</a></p>}
|
parameters = yt["data-parameters"]
|
||||||
rescue URI::InvalidURIError
|
uri = URI("https://www.youtube.com/embed/#{youtube_id}?autoplay=1&#{parameters}")
|
||||||
# remove any invalid/weird URIs
|
yt.replace %{<p><a href="#{uri.to_s}">https://#{uri.host}#{uri.path}</a></p>}
|
||||||
yt.remove
|
rescue URI::InvalidURIError
|
||||||
|
# remove any invalid/weird URIs
|
||||||
|
yt.remove
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class DiscoursePoll::PollsController < ::ApplicationController
|
class DiscoursePoll::PollsController < ::ApplicationController
|
||||||
requires_plugin DiscoursePoll::PLUGIN_NAME
|
requires_plugin DiscoursePoll::PLUGIN_NAME
|
||||||
|
|
||||||
before_action :ensure_logged_in, except: [:voters, :grouped_poll_results]
|
before_action :ensure_logged_in, except: %i[voters grouped_poll_results]
|
||||||
|
|
||||||
def vote
|
def vote
|
||||||
post_id = params.require(:post_id)
|
post_id = params.require(:post_id)
|
||||||
|
@ -63,8 +63,14 @@ class DiscoursePoll::PollsController < ::ApplicationController
|
||||||
|
|
||||||
begin
|
begin
|
||||||
render json: {
|
render json: {
|
||||||
grouped_results: DiscoursePoll::Poll.grouped_poll_results(current_user, post_id, poll_name, user_field_name)
|
grouped_results:
|
||||||
}
|
DiscoursePoll::Poll.grouped_poll_results(
|
||||||
|
current_user,
|
||||||
|
post_id,
|
||||||
|
poll_name,
|
||||||
|
user_field_name,
|
||||||
|
),
|
||||||
|
}
|
||||||
rescue DiscoursePoll::Error => e
|
rescue DiscoursePoll::Error => e
|
||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,33 +9,15 @@ class Poll < ActiveRecord::Base
|
||||||
has_many :poll_options, -> { order(:id) }, dependent: :destroy
|
has_many :poll_options, -> { order(:id) }, dependent: :destroy
|
||||||
has_many :poll_votes
|
has_many :poll_votes
|
||||||
|
|
||||||
enum type: {
|
enum type: { regular: 0, multiple: 1, number: 2 }, _scopes: false
|
||||||
regular: 0,
|
|
||||||
multiple: 1,
|
|
||||||
number: 2,
|
|
||||||
}, _scopes: false
|
|
||||||
|
|
||||||
enum status: {
|
enum status: { open: 0, closed: 1 }, _scopes: false
|
||||||
open: 0,
|
|
||||||
closed: 1,
|
|
||||||
}, _scopes: false
|
|
||||||
|
|
||||||
enum results: {
|
enum results: { always: 0, on_vote: 1, on_close: 2, staff_only: 3 }, _scopes: false
|
||||||
always: 0,
|
|
||||||
on_vote: 1,
|
|
||||||
on_close: 2,
|
|
||||||
staff_only: 3,
|
|
||||||
}, _scopes: false
|
|
||||||
|
|
||||||
enum visibility: {
|
enum visibility: { secret: 0, everyone: 1 }, _scopes: false
|
||||||
secret: 0,
|
|
||||||
everyone: 1,
|
|
||||||
}, _scopes: false
|
|
||||||
|
|
||||||
enum chart_type: {
|
enum chart_type: { bar: 0, pie: 1 }, _scopes: false
|
||||||
bar: 0,
|
|
||||||
pie: 1
|
|
||||||
}, _scopes: false
|
|
||||||
|
|
||||||
validates :min, numericality: { allow_nil: true, only_integer: true, greater_than_or_equal_to: 0 }
|
validates :min, numericality: { allow_nil: true, only_integer: true, greater_than_or_equal_to: 0 }
|
||||||
validates :max, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
|
validates :max, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
|
||||||
|
|
|
@ -48,13 +48,15 @@ class PollSerializer < ApplicationSerializer
|
||||||
PollOptionSerializer.new(
|
PollOptionSerializer.new(
|
||||||
option,
|
option,
|
||||||
root: false,
|
root: false,
|
||||||
scope: { can_see_results: can_see_results }
|
scope: {
|
||||||
|
can_see_results: can_see_results,
|
||||||
|
},
|
||||||
).as_json
|
).as_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def voters
|
def voters
|
||||||
object.poll_votes.count('DISTINCT user_id') + object.anonymous_voters.to_i
|
object.poll_votes.count("DISTINCT user_id") + object.anonymous_voters.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
|
@ -72,5 +74,4 @@ class PollSerializer < ApplicationSerializer
|
||||||
def include_preloaded_voters?
|
def include_preloaded_voters?
|
||||||
object.can_see_voters?(scope.user)
|
object.can_see_voters?(scope.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RenameTotalVotesToVoters < ActiveRecord::Migration[4.2]
|
class RenameTotalVotesToVoters < ActiveRecord::Migration[4.2]
|
||||||
|
|
||||||
def up
|
def up
|
||||||
PostCustomField.where(name: "polls").find_each do |pcf|
|
PostCustomField
|
||||||
polls = ::JSON.parse(pcf.value)
|
.where(name: "polls")
|
||||||
polls.each_value do |poll|
|
.find_each do |pcf|
|
||||||
next if poll.has_key?("voters")
|
polls = ::JSON.parse(pcf.value)
|
||||||
poll["voters"] = poll["total_votes"]
|
polls.each_value do |poll|
|
||||||
poll.delete("total_votes")
|
next if poll.has_key?("voters")
|
||||||
|
poll["voters"] = poll["total_votes"]
|
||||||
|
poll.delete("total_votes")
|
||||||
|
end
|
||||||
|
pcf.value = polls.to_json
|
||||||
|
pcf.save
|
||||||
end
|
end
|
||||||
pcf.value = polls.to_json
|
|
||||||
pcf.save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
PostCustomField.where(name: "polls").find_each do |pcf|
|
PostCustomField
|
||||||
polls = ::JSON.parse(pcf.value)
|
.where(name: "polls")
|
||||||
polls.each_value do |poll|
|
.find_each do |pcf|
|
||||||
next if poll.has_key?("total_votes")
|
polls = ::JSON.parse(pcf.value)
|
||||||
poll["total_votes"] = poll["voters"]
|
polls.each_value do |poll|
|
||||||
poll.delete("voters")
|
next if poll.has_key?("total_votes")
|
||||||
|
poll["total_votes"] = poll["voters"]
|
||||||
|
poll.delete("voters")
|
||||||
|
end
|
||||||
|
pcf.value = polls.to_json
|
||||||
|
pcf.save
|
||||||
end
|
end
|
||||||
pcf.value = polls.to_json
|
|
||||||
pcf.save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class MergePollsVotes < ActiveRecord::Migration[4.2]
|
class MergePollsVotes < ActiveRecord::Migration[4.2]
|
||||||
|
|
||||||
def up
|
def up
|
||||||
PostCustomField.where(name: "polls").order(:post_id).pluck(:post_id).each do |post_id|
|
PostCustomField
|
||||||
polls_votes = {}
|
.where(name: "polls")
|
||||||
PostCustomField.where(post_id: post_id).where("name LIKE 'polls-votes-%'").find_each do |pcf|
|
.order(:post_id)
|
||||||
user_id = pcf.name["polls-votes-".size..-1]
|
.pluck(:post_id)
|
||||||
polls_votes["#{user_id}"] = ::JSON.parse(pcf.value || "{}")
|
.each do |post_id|
|
||||||
end
|
polls_votes = {}
|
||||||
|
PostCustomField
|
||||||
|
.where(post_id: post_id)
|
||||||
|
.where("name LIKE 'polls-votes-%'")
|
||||||
|
.find_each do |pcf|
|
||||||
|
user_id = pcf.name["polls-votes-".size..-1]
|
||||||
|
polls_votes["#{user_id}"] = ::JSON.parse(pcf.value || "{}")
|
||||||
|
end
|
||||||
|
|
||||||
pcf = PostCustomField.find_or_create_by(name: "polls-votes", post_id: post_id)
|
pcf = PostCustomField.find_or_create_by(name: "polls-votes", post_id: post_id)
|
||||||
pcf.value = ::JSON.parse(pcf.value || "{}").merge(polls_votes).to_json
|
pcf.value = ::JSON.parse(pcf.value || "{}").merge(polls_votes).to_json
|
||||||
pcf.save
|
pcf.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ClosePollsInClosedTopics < ActiveRecord::Migration[4.2]
|
class ClosePollsInClosedTopics < ActiveRecord::Migration[4.2]
|
||||||
|
|
||||||
def up
|
def up
|
||||||
PostCustomField.joins(post: :topic)
|
PostCustomField
|
||||||
|
.joins(post: :topic)
|
||||||
.where("post_custom_fields.name = 'polls'")
|
.where("post_custom_fields.name = 'polls'")
|
||||||
.where("topics.closed")
|
.where("topics.closed")
|
||||||
.find_each do |pcf|
|
.find_each do |pcf|
|
||||||
polls = ::JSON.parse(pcf.value || "{}")
|
polls = ::JSON.parse(pcf.value || "{}")
|
||||||
polls.values.each { |poll| poll["status"] = "closed" }
|
polls.values.each { |poll| poll["status"] = "closed" }
|
||||||
pcf.value = polls.to_json
|
pcf.value = polls.to_json
|
||||||
pcf.save
|
pcf.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ class CreatePollsTables < ActiveRecord::Migration[5.2]
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :polls, [:post_id, :name], unique: true
|
add_index :polls, %i[post_id name], unique: true
|
||||||
|
|
||||||
create_table :poll_options do |t|
|
create_table :poll_options do |t|
|
||||||
t.references :poll, index: true, foreign_key: true
|
t.references :poll, index: true, foreign_key: true
|
||||||
|
@ -27,7 +27,7 @@ class CreatePollsTables < ActiveRecord::Migration[5.2]
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :poll_options, [:poll_id, :digest], unique: true
|
add_index :poll_options, %i[poll_id digest], unique: true
|
||||||
|
|
||||||
create_table :poll_votes, id: false do |t|
|
create_table :poll_votes, id: false do |t|
|
||||||
t.references :poll, foreign_key: true
|
t.references :poll, foreign_key: true
|
||||||
|
@ -36,6 +36,6 @@ class CreatePollsTables < ActiveRecord::Migration[5.2]
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :poll_votes, [:poll_id, :poll_option_id, :user_id], unique: true
|
add_index :poll_votes, %i[poll_id poll_option_id user_id], unique: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,11 +5,7 @@ class MigratePollsData < ActiveRecord::Migration[5.2]
|
||||||
PG::Connection.escape_string(text)
|
PG::Connection.escape_string(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
POLL_TYPES ||= {
|
POLL_TYPES ||= { "regular" => 0, "multiple" => 1, "number" => 2 }
|
||||||
"regular" => 0,
|
|
||||||
"multiple" => 1,
|
|
||||||
"number" => 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_INTEGER_MAX ||= 2_147_483_647
|
PG_INTEGER_MAX ||= 2_147_483_647
|
||||||
|
|
||||||
|
@ -61,43 +57,59 @@ class MigratePollsData < ActiveRecord::Migration[5.2]
|
||||||
ORDER BY polls.post_id
|
ORDER BY polls.post_id
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
DB.query(sql).each do |r|
|
DB
|
||||||
# for some reasons, polls or votes might be an array
|
.query(sql)
|
||||||
r.polls = r.polls[0] if Array === r.polls && r.polls.size > 0
|
.each do |r|
|
||||||
r.votes = r.votes[0] if Array === r.votes && r.votes.size > 0
|
# for some reasons, polls or votes might be an array
|
||||||
|
r.polls = r.polls[0] if Array === r.polls && r.polls.size > 0
|
||||||
|
r.votes = r.votes[0] if Array === r.votes && r.votes.size > 0
|
||||||
|
|
||||||
existing_user_ids = User.where(id: r.votes.keys).pluck(:id).to_set
|
existing_user_ids = User.where(id: r.votes.keys).pluck(:id).to_set
|
||||||
|
|
||||||
# Poll votes are stored in a JSON object with the following hierarchy
|
# Poll votes are stored in a JSON object with the following hierarchy
|
||||||
# user_id -> poll_name -> options
|
# user_id -> poll_name -> options
|
||||||
# Since we're iterating over polls, we need to change the hierarchy to
|
# Since we're iterating over polls, we need to change the hierarchy to
|
||||||
# poll_name -> user_id -> options
|
# poll_name -> user_id -> options
|
||||||
|
|
||||||
votes = {}
|
votes = {}
|
||||||
r.votes.each do |user_id, user_votes|
|
r.votes.each do |user_id, user_votes|
|
||||||
# don't migrate votes from deleted/non-existing users
|
# don't migrate votes from deleted/non-existing users
|
||||||
next unless existing_user_ids.include?(user_id.to_i)
|
next unless existing_user_ids.include?(user_id.to_i)
|
||||||
|
|
||||||
user_votes.each do |poll_name, options|
|
user_votes.each do |poll_name, options|
|
||||||
votes[poll_name] ||= {}
|
votes[poll_name] ||= {}
|
||||||
votes[poll_name][user_id] = options
|
votes[poll_name][user_id] = options
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
r.polls.values.each do |poll|
|
r.polls.values.each do |poll|
|
||||||
name = escape(poll["name"].presence || "poll")
|
name = escape(poll["name"].presence || "poll")
|
||||||
type = POLL_TYPES[(poll["type"].presence || "")[/(regular|multiple|number)/, 1] || "regular"]
|
type =
|
||||||
status = poll["status"] == "open" ? 0 : 1
|
POLL_TYPES[(poll["type"].presence || "")[/(regular|multiple|number)/, 1] || "regular"]
|
||||||
visibility = poll["public"] == "true" ? 1 : 0
|
status = poll["status"] == "open" ? 0 : 1
|
||||||
close_at = (Time.zone.parse(poll["close"]) rescue nil)
|
visibility = poll["public"] == "true" ? 1 : 0
|
||||||
min = poll["min"].to_i.clamp(0, PG_INTEGER_MAX)
|
close_at =
|
||||||
max = poll["max"].to_i.clamp(0, PG_INTEGER_MAX)
|
(
|
||||||
step = poll["step"].to_i.clamp(0, max)
|
begin
|
||||||
anonymous_voters = poll["anonymous_voters"].to_i.clamp(0, PG_INTEGER_MAX)
|
Time.zone.parse(poll["close"])
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
)
|
||||||
|
min = poll["min"].to_i.clamp(0, PG_INTEGER_MAX)
|
||||||
|
max = poll["max"].to_i.clamp(0, PG_INTEGER_MAX)
|
||||||
|
step = poll["step"].to_i.clamp(0, max)
|
||||||
|
anonymous_voters = poll["anonymous_voters"].to_i.clamp(0, PG_INTEGER_MAX)
|
||||||
|
|
||||||
next if DB.query_single("SELECT COUNT(*) FROM polls WHERE post_id = ? AND name = ? LIMIT 1", r.post_id, name).first > 0
|
if DB.query_single(
|
||||||
|
"SELECT COUNT(*) FROM polls WHERE post_id = ? AND name = ? LIMIT 1",
|
||||||
|
r.post_id,
|
||||||
|
name,
|
||||||
|
).first > 0
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
poll_id = execute(<<~SQL
|
poll_id = execute(<<~SQL)[0]["id"]
|
||||||
INSERT INTO polls (
|
INSERT INTO polls (
|
||||||
post_id,
|
post_id,
|
||||||
name,
|
name,
|
||||||
|
@ -126,38 +138,41 @@ class MigratePollsData < ActiveRecord::Migration[5.2]
|
||||||
'#{r.updated_at}'
|
'#{r.updated_at}'
|
||||||
) RETURNING id
|
) RETURNING id
|
||||||
SQL
|
SQL
|
||||||
)[0]["id"]
|
|
||||||
|
|
||||||
option_ids = Hash[*DB.query_single(<<~SQL
|
option_ids = Hash[*DB.query_single(<<~SQL)]
|
||||||
INSERT INTO poll_options
|
INSERT INTO poll_options
|
||||||
(poll_id, digest, html, anonymous_votes, created_at, updated_at)
|
(poll_id, digest, html, anonymous_votes, created_at, updated_at)
|
||||||
VALUES
|
VALUES
|
||||||
#{poll["options"].map { |option|
|
#{
|
||||||
"(#{poll_id}, '#{escape(option["id"])}', '#{escape(option["html"].strip)}', #{option["anonymous_votes"].to_i}, '#{r.created_at}', '#{r.updated_at}')" }.join(",")
|
poll["options"]
|
||||||
}
|
.map do |option|
|
||||||
|
"(#{poll_id}, '#{escape(option["id"])}', '#{escape(option["html"].strip)}', #{option["anonymous_votes"].to_i}, '#{r.created_at}', '#{r.updated_at}')"
|
||||||
|
end
|
||||||
|
.join(",")
|
||||||
|
}
|
||||||
RETURNING digest, id
|
RETURNING digest, id
|
||||||
SQL
|
SQL
|
||||||
)]
|
|
||||||
|
|
||||||
if votes[name].present?
|
if votes[name].present?
|
||||||
poll_votes = votes[name].map do |user_id, options|
|
poll_votes =
|
||||||
options
|
votes[name].map do |user_id, options|
|
||||||
.select { |o| option_ids.has_key?(o) }
|
options
|
||||||
.map { |o| "(#{poll_id}, #{option_ids[o]}, #{user_id.to_i}, '#{r.created_at}', '#{r.updated_at}')" }
|
.select { |o| option_ids.has_key?(o) }
|
||||||
end
|
.map do |o|
|
||||||
|
"(#{poll_id}, #{option_ids[o]}, #{user_id.to_i}, '#{r.created_at}', '#{r.updated_at}')"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
poll_votes.flatten!
|
poll_votes.flatten!
|
||||||
poll_votes.uniq!
|
poll_votes.uniq!
|
||||||
|
|
||||||
if poll_votes.present?
|
execute <<~SQL if poll_votes.present?
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO poll_votes (poll_id, poll_option_id, user_id, created_at, updated_at)
|
INSERT INTO poll_votes (poll_id, poll_option_id, user_id, created_at, updated_at)
|
||||||
VALUES #{poll_votes.join(",")}
|
VALUES #{poll_votes.join(",")}
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
execute <<~SQL
|
||||||
INSERT INTO post_custom_fields (name, value, post_id, created_at, updated_at)
|
INSERT INTO post_custom_fields (name, value, post_id, created_at, updated_at)
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Jobs
|
module Jobs
|
||||||
|
|
||||||
class ClosePoll < ::Jobs::Base
|
class ClosePoll < ::Jobs::Base
|
||||||
|
|
||||||
def execute(args)
|
def execute(args)
|
||||||
%i{
|
%i[post_id poll_name].each do |key|
|
||||||
post_id
|
|
||||||
poll_name
|
|
||||||
}.each do |key|
|
|
||||||
raise Discourse::InvalidParameters.new(key) if args[key].blank?
|
raise Discourse::InvalidParameters.new(key) if args[key].blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -17,10 +12,8 @@ module Jobs
|
||||||
args[:post_id],
|
args[:post_id],
|
||||||
args[:poll_name],
|
args[:poll_name],
|
||||||
"closed",
|
"closed",
|
||||||
false
|
false,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,38 +4,42 @@ class DiscoursePoll::Poll
|
||||||
def self.vote(user, post_id, poll_name, options)
|
def self.vote(user, post_id, poll_name, options)
|
||||||
poll_id = nil
|
poll_id = nil
|
||||||
|
|
||||||
serialized_poll = DiscoursePoll::Poll.change_vote(user, post_id, poll_name) do |poll|
|
serialized_poll =
|
||||||
poll_id = poll.id
|
DiscoursePoll::Poll.change_vote(user, post_id, poll_name) do |poll|
|
||||||
# remove options that aren't available in the poll
|
poll_id = poll.id
|
||||||
available_options = poll.poll_options.map { |o| o.digest }.to_set
|
# remove options that aren't available in the poll
|
||||||
options.select! { |o| available_options.include?(o) }
|
available_options = poll.poll_options.map { |o| o.digest }.to_set
|
||||||
|
options.select! { |o| available_options.include?(o) }
|
||||||
|
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.requires_at_least_1_valid_option") if options.empty?
|
if options.empty?
|
||||||
|
raise DiscoursePoll::Error.new I18n.t("poll.requires_at_least_1_valid_option")
|
||||||
|
end
|
||||||
|
|
||||||
new_option_ids = poll.poll_options.each_with_object([]) do |option, obj|
|
new_option_ids =
|
||||||
obj << option.id if options.include?(option.digest)
|
poll
|
||||||
end
|
.poll_options
|
||||||
|
.each_with_object([]) do |option, obj|
|
||||||
|
obj << option.id if options.include?(option.digest)
|
||||||
|
end
|
||||||
|
|
||||||
self.validate_votes!(poll, new_option_ids)
|
self.validate_votes!(poll, new_option_ids)
|
||||||
|
|
||||||
old_option_ids = poll.poll_options.each_with_object([]) do |option, obj|
|
old_option_ids =
|
||||||
if option.poll_votes.where(user_id: user.id).exists?
|
poll
|
||||||
obj << option.id
|
.poll_options
|
||||||
|
.each_with_object([]) do |option, obj|
|
||||||
|
obj << option.id if option.poll_votes.where(user_id: user.id).exists?
|
||||||
|
end
|
||||||
|
|
||||||
|
# remove non-selected votes
|
||||||
|
PollVote.where(poll: poll, user: user).where.not(poll_option_id: new_option_ids).delete_all
|
||||||
|
|
||||||
|
# create missing votes
|
||||||
|
(new_option_ids - old_option_ids).each do |option_id|
|
||||||
|
PollVote.create!(poll: poll, user: user, poll_option_id: option_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove non-selected votes
|
|
||||||
PollVote
|
|
||||||
.where(poll: poll, user: user)
|
|
||||||
.where.not(poll_option_id: new_option_ids)
|
|
||||||
.delete_all
|
|
||||||
|
|
||||||
# create missing votes
|
|
||||||
(new_option_ids - old_option_ids).each do |option_id|
|
|
||||||
PollVote.create!(poll: poll, user: user, poll_option_id: option_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ensure consistency here as we do not have a unique index to limit the
|
# Ensure consistency here as we do not have a unique index to limit the
|
||||||
# number of votes per the poll's configuration.
|
# number of votes per the poll's configuration.
|
||||||
is_multiple = serialized_poll[:type] == "multiple"
|
is_multiple = serialized_poll[:type] == "multiple"
|
||||||
|
@ -79,20 +83,26 @@ class DiscoursePoll::Poll
|
||||||
|
|
||||||
# topic must not be archived
|
# topic must not be archived
|
||||||
if post.topic&.archived
|
if post.topic&.archived
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.topic_must_be_open_to_toggle_status") if raise_errors
|
if raise_errors
|
||||||
|
raise DiscoursePoll::Error.new I18n.t("poll.topic_must_be_open_to_toggle_status")
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# either staff member or OP
|
# either staff member or OP
|
||||||
unless post.user_id == user&.id || user&.staff?
|
unless post.user_id == user&.id || user&.staff?
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.only_staff_or_op_can_toggle_status") if raise_errors
|
if raise_errors
|
||||||
|
raise DiscoursePoll::Error.new I18n.t("poll.only_staff_or_op_can_toggle_status")
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
poll = Poll.find_by(post_id: post_id, name: poll_name)
|
poll = Poll.find_by(post_id: post_id, name: poll_name)
|
||||||
|
|
||||||
if !poll
|
if !poll
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.no_poll_with_this_name", name: poll_name) if raise_errors
|
if raise_errors
|
||||||
|
raise DiscoursePoll::Error.new I18n.t("poll.no_poll_with_this_name", name: poll_name)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -110,7 +120,7 @@ class DiscoursePoll::Poll
|
||||||
|
|
||||||
def self.serialized_voters(poll, opts = {})
|
def self.serialized_voters(poll, opts = {})
|
||||||
limit = (opts["limit"] || 25).to_i
|
limit = (opts["limit"] || 25).to_i
|
||||||
limit = 0 if limit < 0
|
limit = 0 if limit < 0
|
||||||
limit = 50 if limit > 50
|
limit = 50 if limit > 50
|
||||||
|
|
||||||
page = (opts["page"] || 1).to_i
|
page = (opts["page"] || 1).to_i
|
||||||
|
@ -121,13 +131,14 @@ class DiscoursePoll::Poll
|
||||||
option_digest = opts["option_id"].to_s
|
option_digest = opts["option_id"].to_s
|
||||||
|
|
||||||
if poll.number?
|
if poll.number?
|
||||||
user_ids = PollVote
|
user_ids =
|
||||||
.where(poll: poll)
|
PollVote
|
||||||
.group(:user_id)
|
.where(poll: poll)
|
||||||
.order("MIN(created_at)")
|
.group(:user_id)
|
||||||
.offset(offset)
|
.order("MIN(created_at)")
|
||||||
.limit(limit)
|
.offset(offset)
|
||||||
.pluck(:user_id)
|
.limit(limit)
|
||||||
|
.pluck(:user_id)
|
||||||
|
|
||||||
result = User.where(id: user_ids).map { |u| UserNameSerializer.new(u).serializable_hash }
|
result = User.where(id: user_ids).map { |u| UserNameSerializer.new(u).serializable_hash }
|
||||||
elsif option_digest.present?
|
elsif option_digest.present?
|
||||||
|
@ -135,13 +146,14 @@ class DiscoursePoll::Poll
|
||||||
|
|
||||||
raise Discourse::InvalidParameters.new(:option_id) unless poll_option
|
raise Discourse::InvalidParameters.new(:option_id) unless poll_option
|
||||||
|
|
||||||
user_ids = PollVote
|
user_ids =
|
||||||
.where(poll: poll, poll_option: poll_option)
|
PollVote
|
||||||
.group(:user_id)
|
.where(poll: poll, poll_option: poll_option)
|
||||||
.order("MIN(created_at)")
|
.group(:user_id)
|
||||||
.offset(offset)
|
.order("MIN(created_at)")
|
||||||
.limit(limit)
|
.offset(offset)
|
||||||
.pluck(:user_id)
|
.limit(limit)
|
||||||
|
.pluck(:user_id)
|
||||||
|
|
||||||
user_hashes = User.where(id: user_ids).map { |u| UserNameSerializer.new(u).serializable_hash }
|
user_hashes = User.where(id: user_ids).map { |u| UserNameSerializer.new(u).serializable_hash }
|
||||||
|
|
||||||
|
@ -163,10 +175,11 @@ class DiscoursePoll::Poll
|
||||||
|
|
||||||
user_ids = votes.map(&:user_id).uniq
|
user_ids = votes.map(&:user_id).uniq
|
||||||
|
|
||||||
user_hashes = User
|
user_hashes =
|
||||||
.where(id: user_ids)
|
User
|
||||||
.map { |u| [u.id, UserNameSerializer.new(u).serializable_hash] }
|
.where(id: user_ids)
|
||||||
.to_h
|
.map { |u| [u.id, UserNameSerializer.new(u).serializable_hash] }
|
||||||
|
.to_h
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
votes.each do |v|
|
votes.each do |v|
|
||||||
|
@ -186,10 +199,13 @@ class DiscoursePoll::Poll
|
||||||
def self.grouped_poll_results(user, post_id, poll_name, user_field_name)
|
def self.grouped_poll_results(user, post_id, poll_name, user_field_name)
|
||||||
raise Discourse::InvalidParameters.new(:post_id) if !Post.where(id: post_id).exists?
|
raise Discourse::InvalidParameters.new(:post_id) if !Post.where(id: post_id).exists?
|
||||||
|
|
||||||
poll = Poll.includes(:poll_options).includes(:poll_votes).find_by(post_id: post_id, name: poll_name)
|
poll =
|
||||||
|
Poll.includes(:poll_options).includes(:poll_votes).find_by(post_id: post_id, name: poll_name)
|
||||||
raise Discourse::InvalidParameters.new(:poll_name) unless poll
|
raise Discourse::InvalidParameters.new(:poll_name) unless poll
|
||||||
|
|
||||||
raise Discourse::InvalidParameters.new(:user_field_name) unless SiteSetting.poll_groupable_user_fields.split('|').include?(user_field_name)
|
unless SiteSetting.poll_groupable_user_fields.split("|").include?(user_field_name)
|
||||||
|
raise Discourse::InvalidParameters.new(:user_field_name)
|
||||||
|
end
|
||||||
|
|
||||||
poll_votes = poll.poll_votes
|
poll_votes = poll.poll_votes
|
||||||
|
|
||||||
|
@ -199,7 +215,11 @@ class DiscoursePoll::Poll
|
||||||
end
|
end
|
||||||
|
|
||||||
user_ids = poll_votes.map(&:user_id).uniq
|
user_ids = poll_votes.map(&:user_id).uniq
|
||||||
user_fields = UserCustomField.where(user_id: user_ids, name: transform_for_user_field_override(user_field_name))
|
user_fields =
|
||||||
|
UserCustomField.where(
|
||||||
|
user_id: user_ids,
|
||||||
|
name: transform_for_user_field_override(user_field_name),
|
||||||
|
)
|
||||||
|
|
||||||
user_field_map = {}
|
user_field_map = {}
|
||||||
user_fields.each do |f|
|
user_fields.each do |f|
|
||||||
|
@ -207,78 +227,80 @@ class DiscoursePoll::Poll
|
||||||
user_field_map[f.user_id] = f.value
|
user_field_map[f.user_id] = f.value
|
||||||
end
|
end
|
||||||
|
|
||||||
votes_with_field = poll_votes.map do |vote|
|
votes_with_field =
|
||||||
v = vote.attributes
|
poll_votes.map do |vote|
|
||||||
v[:field_value] = user_field_map[vote.user_id]
|
v = vote.attributes
|
||||||
v
|
v[:field_value] = user_field_map[vote.user_id]
|
||||||
end
|
v
|
||||||
|
end
|
||||||
|
|
||||||
chart_data = []
|
chart_data = []
|
||||||
votes_with_field.group_by { |vote| vote[:field_value] }.each do |field_answer, votes|
|
votes_with_field
|
||||||
grouped_selected_options = {}
|
.group_by { |vote| vote[:field_value] }
|
||||||
|
.each do |field_answer, votes|
|
||||||
|
grouped_selected_options = {}
|
||||||
|
|
||||||
# Create all the options with 0 votes. This ensures all the charts will have the same order of options, and same colors per option.
|
# Create all the options with 0 votes. This ensures all the charts will have the same order of options, and same colors per option.
|
||||||
poll_options.each do |id, option|
|
poll_options.each do |id, option|
|
||||||
grouped_selected_options[id] = {
|
grouped_selected_options[id] = { digest: option[:digest], html: option[:html], votes: 0 }
|
||||||
digest: option[:digest],
|
end
|
||||||
html: option[:html],
|
|
||||||
votes: 0
|
# Now go back and update the vote counts. Using hashes so we dont have n^2
|
||||||
}
|
votes
|
||||||
|
.group_by { |v| v["poll_option_id"] }
|
||||||
|
.each do |option_id, votes_for_option|
|
||||||
|
grouped_selected_options[option_id.to_s][:votes] = votes_for_option.length
|
||||||
|
end
|
||||||
|
|
||||||
|
group_label = field_answer ? field_answer.titleize : I18n.t("poll.user_field.no_data")
|
||||||
|
chart_data << { group: group_label, options: grouped_selected_options.values }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Now go back and update the vote counts. Using hashes so we dont have n^2
|
|
||||||
votes.group_by { |v| v["poll_option_id"] }.each do |option_id, votes_for_option|
|
|
||||||
grouped_selected_options[option_id.to_s][:votes] = votes_for_option.length
|
|
||||||
end
|
|
||||||
|
|
||||||
group_label = field_answer ? field_answer.titleize : I18n.t("poll.user_field.no_data")
|
|
||||||
chart_data << { group: group_label, options: grouped_selected_options.values }
|
|
||||||
end
|
|
||||||
chart_data
|
chart_data
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.schedule_jobs(post)
|
def self.schedule_jobs(post)
|
||||||
Poll.where(post: post).find_each do |poll|
|
Poll
|
||||||
job_args = {
|
.where(post: post)
|
||||||
post_id: post.id,
|
.find_each do |poll|
|
||||||
poll_name: poll.name
|
job_args = { post_id: post.id, poll_name: poll.name }
|
||||||
}
|
|
||||||
|
|
||||||
Jobs.cancel_scheduled_job(:close_poll, job_args)
|
Jobs.cancel_scheduled_job(:close_poll, job_args)
|
||||||
|
|
||||||
if poll.open? && poll.close_at && poll.close_at > Time.zone.now
|
if poll.open? && poll.close_at && poll.close_at > Time.zone.now
|
||||||
Jobs.enqueue_at(poll.close_at, :close_poll, job_args)
|
Jobs.enqueue_at(poll.close_at, :close_poll, job_args)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create!(post_id, poll)
|
def self.create!(post_id, poll)
|
||||||
close_at = begin
|
close_at =
|
||||||
Time.zone.parse(poll["close"] || '')
|
begin
|
||||||
rescue ArgumentError
|
Time.zone.parse(poll["close"] || "")
|
||||||
end
|
rescue ArgumentError
|
||||||
|
end
|
||||||
|
|
||||||
created_poll = Poll.create!(
|
created_poll =
|
||||||
post_id: post_id,
|
Poll.create!(
|
||||||
name: poll["name"].presence || "poll",
|
post_id: post_id,
|
||||||
close_at: close_at,
|
name: poll["name"].presence || "poll",
|
||||||
type: poll["type"].presence || "regular",
|
close_at: close_at,
|
||||||
status: poll["status"].presence || "open",
|
type: poll["type"].presence || "regular",
|
||||||
visibility: poll["public"] == "true" ? "everyone" : "secret",
|
status: poll["status"].presence || "open",
|
||||||
title: poll["title"],
|
visibility: poll["public"] == "true" ? "everyone" : "secret",
|
||||||
results: poll["results"].presence || "always",
|
title: poll["title"],
|
||||||
min: poll["min"],
|
results: poll["results"].presence || "always",
|
||||||
max: poll["max"],
|
min: poll["min"],
|
||||||
step: poll["step"],
|
max: poll["max"],
|
||||||
chart_type: poll["charttype"] || "bar",
|
step: poll["step"],
|
||||||
groups: poll["groups"]
|
chart_type: poll["charttype"] || "bar",
|
||||||
)
|
groups: poll["groups"],
|
||||||
|
)
|
||||||
|
|
||||||
poll["options"].each do |option|
|
poll["options"].each do |option|
|
||||||
PollOption.create!(
|
PollOption.create!(
|
||||||
poll: created_poll,
|
poll: created_poll,
|
||||||
digest: option["id"].presence,
|
digest: option["id"].presence,
|
||||||
html: option["html"].presence&.strip
|
html: option["html"].presence&.strip,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -286,33 +308,38 @@ class DiscoursePoll::Poll
|
||||||
def self.extract(raw, topic_id, user_id = nil)
|
def self.extract(raw, topic_id, user_id = nil)
|
||||||
# TODO: we should fix the callback mess so that the cooked version is available
|
# TODO: we should fix the callback mess so that the cooked version is available
|
||||||
# in the validators instead of cooking twice
|
# in the validators instead of cooking twice
|
||||||
raw = raw.sub(/\[quote.+\/quote\]/m, '')
|
raw = raw.sub(%r{\[quote.+/quote\]}m, "")
|
||||||
cooked = PrettyText.cook(raw, topic_id: topic_id, user_id: user_id)
|
cooked = PrettyText.cook(raw, topic_id: topic_id, user_id: user_id)
|
||||||
|
|
||||||
Nokogiri::HTML5(cooked).css("div.poll").map do |p|
|
Nokogiri
|
||||||
poll = { "options" => [], "name" => DiscoursePoll::DEFAULT_POLL_NAME }
|
.HTML5(cooked)
|
||||||
|
.css("div.poll")
|
||||||
|
.map do |p|
|
||||||
|
poll = { "options" => [], "name" => DiscoursePoll::DEFAULT_POLL_NAME }
|
||||||
|
|
||||||
# attributes
|
# attributes
|
||||||
p.attributes.values.each do |attribute|
|
p.attributes.values.each do |attribute|
|
||||||
if attribute.name.start_with?(DiscoursePoll::DATA_PREFIX)
|
if attribute.name.start_with?(DiscoursePoll::DATA_PREFIX)
|
||||||
poll[attribute.name[DiscoursePoll::DATA_PREFIX.length..-1]] = CGI.escapeHTML(attribute.value || "")
|
poll[attribute.name[DiscoursePoll::DATA_PREFIX.length..-1]] = CGI.escapeHTML(
|
||||||
|
attribute.value || "",
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# options
|
# options
|
||||||
p.css("li[#{DiscoursePoll::DATA_PREFIX}option-id]").each do |o|
|
p
|
||||||
option_id = o.attributes[DiscoursePoll::DATA_PREFIX + "option-id"].value.to_s
|
.css("li[#{DiscoursePoll::DATA_PREFIX}option-id]")
|
||||||
poll["options"] << { "id" => option_id, "html" => o.inner_html.strip }
|
.each do |o|
|
||||||
end
|
option_id = o.attributes[DiscoursePoll::DATA_PREFIX + "option-id"].value.to_s
|
||||||
|
poll["options"] << { "id" => option_id, "html" => o.inner_html.strip }
|
||||||
|
end
|
||||||
|
|
||||||
# title
|
# title
|
||||||
title_element = p.css(".poll-title").first
|
title_element = p.css(".poll-title").first
|
||||||
if title_element
|
poll["title"] = title_element.inner_html.strip if title_element
|
||||||
poll["title"] = title_element.inner_html.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
poll
|
poll
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.validate_votes!(poll, options)
|
def self.validate_votes!(poll, options)
|
||||||
|
@ -320,15 +347,9 @@ class DiscoursePoll::Poll
|
||||||
|
|
||||||
if poll.multiple?
|
if poll.multiple?
|
||||||
if poll.min && (num_of_options < poll.min)
|
if poll.min && (num_of_options < poll.min)
|
||||||
raise DiscoursePoll::Error.new(I18n.t(
|
raise DiscoursePoll::Error.new(I18n.t("poll.min_vote_per_user", count: poll.min))
|
||||||
"poll.min_vote_per_user",
|
|
||||||
count: poll.min
|
|
||||||
))
|
|
||||||
elsif poll.max && (num_of_options > poll.max)
|
elsif poll.max && (num_of_options > poll.max)
|
||||||
raise DiscoursePoll::Error.new(I18n.t(
|
raise DiscoursePoll::Error.new(I18n.t("poll.max_vote_per_user", count: poll.max))
|
||||||
"poll.max_vote_per_user",
|
|
||||||
count: poll.max
|
|
||||||
))
|
|
||||||
end
|
end
|
||||||
elsif num_of_options > 1
|
elsif num_of_options > 1
|
||||||
raise DiscoursePoll::Error.new(I18n.t("poll.one_vote_per_user"))
|
raise DiscoursePoll::Error.new(I18n.t("poll.one_vote_per_user"))
|
||||||
|
@ -341,9 +362,7 @@ class DiscoursePoll::Poll
|
||||||
post = Post.find_by(id: post_id)
|
post = Post.find_by(id: post_id)
|
||||||
|
|
||||||
# post must not be deleted
|
# post must not be deleted
|
||||||
if post.nil? || post.trashed?
|
raise DiscoursePoll::Error.new I18n.t("poll.post_is_deleted") if post.nil? || post.trashed?
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.post_is_deleted")
|
|
||||||
end
|
|
||||||
|
|
||||||
# topic must not be archived
|
# topic must not be archived
|
||||||
if post.topic&.archived
|
if post.topic&.archived
|
||||||
|
@ -358,7 +377,9 @@ class DiscoursePoll::Poll
|
||||||
|
|
||||||
poll = Poll.includes(:poll_options).find_by(post_id: post_id, name: poll_name)
|
poll = Poll.includes(:poll_options).find_by(post_id: post_id, name: poll_name)
|
||||||
|
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.no_poll_with_this_name", name: poll_name) unless poll
|
unless poll
|
||||||
|
raise DiscoursePoll::Error.new I18n.t("poll.no_poll_with_this_name", name: poll_name)
|
||||||
|
end
|
||||||
raise DiscoursePoll::Error.new I18n.t("poll.poll_must_be_open_to_vote") if poll.is_closed?
|
raise DiscoursePoll::Error.new I18n.t("poll.poll_must_be_open_to_vote") if poll.is_closed?
|
||||||
|
|
||||||
if poll.groups
|
if poll.groups
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
module DiscoursePoll
|
module DiscoursePoll
|
||||||
class PollsUpdater
|
class PollsUpdater
|
||||||
|
POLL_ATTRIBUTES ||= %w[close_at max min results status step type visibility title groups]
|
||||||
POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility title groups}
|
|
||||||
|
|
||||||
def self.update(post, polls)
|
def self.update(post, polls)
|
||||||
::Poll.transaction do
|
::Poll.transaction do
|
||||||
|
@ -24,64 +23,81 @@ module DiscoursePoll
|
||||||
# create polls
|
# create polls
|
||||||
if created_poll_names.present?
|
if created_poll_names.present?
|
||||||
has_changed = true
|
has_changed = true
|
||||||
polls.slice(*created_poll_names).values.each do |poll|
|
polls.slice(*created_poll_names).values.each { |poll| Poll.create!(post.id, poll) }
|
||||||
Poll.create!(post.id, poll)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# update polls
|
# update polls
|
||||||
::Poll.includes(:poll_votes, :poll_options).where(post: post).find_each do |old_poll|
|
::Poll
|
||||||
new_poll = polls[old_poll.name]
|
.includes(:poll_votes, :poll_options)
|
||||||
new_poll_options = new_poll["options"]
|
.where(post: post)
|
||||||
|
.find_each do |old_poll|
|
||||||
|
new_poll = polls[old_poll.name]
|
||||||
|
new_poll_options = new_poll["options"]
|
||||||
|
|
||||||
attributes = new_poll.slice(*POLL_ATTRIBUTES)
|
attributes = new_poll.slice(*POLL_ATTRIBUTES)
|
||||||
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
|
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
|
||||||
attributes["close_at"] = Time.zone.parse(new_poll["close"]) rescue nil
|
attributes["close_at"] = begin
|
||||||
attributes["status"] = old_poll["status"]
|
Time.zone.parse(new_poll["close"])
|
||||||
attributes["groups"] = new_poll["groups"]
|
rescue StandardError
|
||||||
poll = ::Poll.new(attributes)
|
nil
|
||||||
|
end
|
||||||
|
attributes["status"] = old_poll["status"]
|
||||||
|
attributes["groups"] = new_poll["groups"]
|
||||||
|
poll = ::Poll.new(attributes)
|
||||||
|
|
||||||
if is_different?(old_poll, poll, new_poll_options)
|
if is_different?(old_poll, poll, new_poll_options)
|
||||||
|
# only prevent changes when there's at least 1 vote
|
||||||
|
if old_poll.poll_votes.size > 0
|
||||||
|
# can't change after edit window (when enabled)
|
||||||
|
if edit_window > 0 && old_poll.created_at < edit_window.minutes.ago
|
||||||
|
error =
|
||||||
|
(
|
||||||
|
if poll.name == DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
|
I18n.t(
|
||||||
|
"poll.edit_window_expired.cannot_edit_default_poll_with_votes",
|
||||||
|
minutes: edit_window,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
I18n.t(
|
||||||
|
"poll.edit_window_expired.cannot_edit_named_poll_with_votes",
|
||||||
|
minutes: edit_window,
|
||||||
|
name: poll.name,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
# only prevent changes when there's at least 1 vote
|
post.errors.add(:base, error)
|
||||||
if old_poll.poll_votes.size > 0
|
return
|
||||||
# can't change after edit window (when enabled)
|
end
|
||||||
if edit_window > 0 && old_poll.created_at < edit_window.minutes.ago
|
|
||||||
error = poll.name == DiscoursePoll::DEFAULT_POLL_NAME ?
|
|
||||||
I18n.t("poll.edit_window_expired.cannot_edit_default_poll_with_votes", minutes: edit_window) :
|
|
||||||
I18n.t("poll.edit_window_expired.cannot_edit_named_poll_with_votes", minutes: edit_window, name: poll.name)
|
|
||||||
|
|
||||||
post.errors.add(:base, error)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# update poll
|
||||||
|
POLL_ATTRIBUTES.each do |attr|
|
||||||
|
old_poll.public_send("#{attr}=", poll.public_send(attr))
|
||||||
|
end
|
||||||
|
|
||||||
|
old_poll.save!
|
||||||
|
|
||||||
|
# keep track of anonymous votes
|
||||||
|
anonymous_votes =
|
||||||
|
old_poll.poll_options.map { |pv| [pv.digest, pv.anonymous_votes] }.to_h
|
||||||
|
|
||||||
|
# destroy existing options & votes
|
||||||
|
::PollOption.where(poll: old_poll).destroy_all
|
||||||
|
|
||||||
|
# create new options
|
||||||
|
new_poll_options.each do |option|
|
||||||
|
::PollOption.create!(
|
||||||
|
poll: old_poll,
|
||||||
|
digest: option["id"],
|
||||||
|
html: option["html"].strip,
|
||||||
|
anonymous_votes: anonymous_votes[option["id"]],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
has_changed = true
|
||||||
end
|
end
|
||||||
|
|
||||||
# update poll
|
|
||||||
POLL_ATTRIBUTES.each do |attr|
|
|
||||||
old_poll.public_send("#{attr}=", poll.public_send(attr))
|
|
||||||
end
|
|
||||||
|
|
||||||
old_poll.save!
|
|
||||||
|
|
||||||
# keep track of anonymous votes
|
|
||||||
anonymous_votes = old_poll.poll_options.map { |pv| [pv.digest, pv.anonymous_votes] }.to_h
|
|
||||||
|
|
||||||
# destroy existing options & votes
|
|
||||||
::PollOption.where(poll: old_poll).destroy_all
|
|
||||||
|
|
||||||
# create new options
|
|
||||||
new_poll_options.each do |option|
|
|
||||||
::PollOption.create!(
|
|
||||||
poll: old_poll,
|
|
||||||
digest: option["id"],
|
|
||||||
html: option["html"].strip,
|
|
||||||
anonymous_votes: anonymous_votes[option["id"]],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
has_changed = true
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if ::Poll.exists?(post: post)
|
if ::Poll.exists?(post: post)
|
||||||
post.custom_fields[HAS_POLLS] = true
|
post.custom_fields[HAS_POLLS] = true
|
||||||
|
@ -93,7 +109,13 @@ module DiscoursePoll
|
||||||
|
|
||||||
if has_changed
|
if has_changed
|
||||||
polls = ::Poll.includes(poll_options: :poll_votes).where(post: post)
|
polls = ::Poll.includes(poll_options: :poll_votes).where(post: post)
|
||||||
polls = ActiveModel::ArraySerializer.new(polls, each_serializer: PollSerializer, root: false, scope: Guardian.new(nil)).as_json
|
polls =
|
||||||
|
ActiveModel::ArraySerializer.new(
|
||||||
|
polls,
|
||||||
|
each_serializer: PollSerializer,
|
||||||
|
root: false,
|
||||||
|
scope: Guardian.new(nil),
|
||||||
|
).as_json
|
||||||
post.publish_message!("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
post.publish_message!("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -108,11 +130,12 @@ module DiscoursePoll
|
||||||
end
|
end
|
||||||
|
|
||||||
# an option was changed?
|
# an option was changed?
|
||||||
return true if old_poll.poll_options.map { |o| o.digest }.sort != new_options.map { |o| o["id"] }.sort
|
if old_poll.poll_options.map { |o| o.digest }.sort != new_options.map { |o| o["id"] }.sort
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
# it's the same!
|
# it's the same!
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
module DiscoursePoll
|
module DiscoursePoll
|
||||||
class PollsValidator
|
class PollsValidator
|
||||||
|
|
||||||
MAX_VALUE = 2_147_483_647
|
MAX_VALUE = 2_147_483_647
|
||||||
|
|
||||||
def initialize(post)
|
def initialize(post)
|
||||||
|
@ -12,17 +11,19 @@ module DiscoursePoll
|
||||||
def validate_polls
|
def validate_polls
|
||||||
polls = {}
|
polls = {}
|
||||||
|
|
||||||
DiscoursePoll::Poll::extract(@post.raw, @post.topic_id, @post.user_id).each do |poll|
|
DiscoursePoll::Poll
|
||||||
return false unless valid_arguments?(poll)
|
.extract(@post.raw, @post.topic_id, @post.user_id)
|
||||||
return false unless valid_numbers?(poll)
|
.each do |poll|
|
||||||
return false unless unique_poll_name?(polls, poll)
|
return false unless valid_arguments?(poll)
|
||||||
return false unless unique_options?(poll)
|
return false unless valid_numbers?(poll)
|
||||||
return false unless any_blank_options?(poll)
|
return false unless unique_poll_name?(polls, poll)
|
||||||
return false unless at_least_one_option?(poll)
|
return false unless unique_options?(poll)
|
||||||
return false unless valid_number_of_options?(poll)
|
return false unless any_blank_options?(poll)
|
||||||
return false unless valid_multiple_choice_settings?(poll)
|
return false unless at_least_one_option?(poll)
|
||||||
polls[poll["name"]] = poll
|
return false unless valid_number_of_options?(poll)
|
||||||
end
|
return false unless valid_multiple_choice_settings?(poll)
|
||||||
|
polls[poll["name"]] = poll
|
||||||
|
end
|
||||||
|
|
||||||
polls
|
polls
|
||||||
end
|
end
|
||||||
|
@ -33,17 +34,26 @@ module DiscoursePoll
|
||||||
valid = true
|
valid = true
|
||||||
|
|
||||||
if poll["type"].present? && !::Poll.types.has_key?(poll["type"])
|
if poll["type"].present? && !::Poll.types.has_key?(poll["type"])
|
||||||
@post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "type", value: poll["type"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.invalid_argument", argument: "type", value: poll["type"]),
|
||||||
|
)
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
if poll["status"].present? && !::Poll.statuses.has_key?(poll["status"])
|
if poll["status"].present? && !::Poll.statuses.has_key?(poll["status"])
|
||||||
@post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "status", value: poll["status"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.invalid_argument", argument: "status", value: poll["status"]),
|
||||||
|
)
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
if poll["results"].present? && !::Poll.results.has_key?(poll["results"])
|
if poll["results"].present? && !::Poll.results.has_key?(poll["results"])
|
||||||
@post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "results", value: poll["results"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.invalid_argument", argument: "results", value: poll["results"]),
|
||||||
|
)
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -69,7 +79,10 @@ module DiscoursePoll
|
||||||
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_different_options"))
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_different_options"))
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_different_options", name: poll["name"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.named_poll_must_have_different_options", name: poll["name"]),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -83,7 +96,10 @@ module DiscoursePoll
|
||||||
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
@post.errors.add(:base, I18n.t("poll.default_poll_must_not_have_any_empty_options"))
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_not_have_any_empty_options"))
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.named_poll_must_not_have_any_empty_options", name: poll["name"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.named_poll_must_not_have_any_empty_options", name: poll["name"]),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -97,7 +113,10 @@ module DiscoursePoll
|
||||||
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_1_option"))
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_1_option"))
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_1_option", name: poll["name"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.named_poll_must_have_at_least_1_option", name: poll["name"]),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -109,9 +128,22 @@ module DiscoursePoll
|
||||||
def valid_number_of_options?(poll)
|
def valid_number_of_options?(poll)
|
||||||
if poll["options"].size > SiteSetting.poll_maximum_options
|
if poll["options"].size > SiteSetting.poll_maximum_options
|
||||||
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t(
|
||||||
|
"poll.default_poll_must_have_less_options",
|
||||||
|
count: SiteSetting.poll_maximum_options,
|
||||||
|
),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_less_options", name: poll["name"], count: SiteSetting.poll_maximum_options))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t(
|
||||||
|
"poll.named_poll_must_have_less_options",
|
||||||
|
name: poll["name"],
|
||||||
|
count: SiteSetting.poll_maximum_options,
|
||||||
|
),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -128,9 +160,18 @@ module DiscoursePoll
|
||||||
|
|
||||||
if min > max || min <= 0 || max <= 0 || max > options || min >= options
|
if min > max || min <= 0 || max <= 0 || max > options || min >= options
|
||||||
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
@post.errors.add(:base, I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: poll["name"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t(
|
||||||
|
"poll.named_poll_with_multiple_choices_has_invalid_parameters",
|
||||||
|
name: poll["name"],
|
||||||
|
),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -172,7 +213,10 @@ module DiscoursePoll
|
||||||
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_1_option"))
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_1_option"))
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_1_option", name: poll["name"]))
|
@post.errors.add(
|
||||||
|
:base,
|
||||||
|
I18n.t("poll.named_poll_must_have_at_least_1_option", name: poll["name"]),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,13 @@ module DiscoursePoll
|
||||||
def validate_post
|
def validate_post
|
||||||
min_trust_level = SiteSetting.poll_minimum_trust_level_to_create
|
min_trust_level = SiteSetting.poll_minimum_trust_level_to_create
|
||||||
|
|
||||||
if (@post.acting_user && (@post.acting_user.staff? || @post.acting_user.trust_level >= TrustLevel[min_trust_level])) || @post.topic&.pm_with_non_human_user?
|
if (
|
||||||
|
@post.acting_user &&
|
||||||
|
(
|
||||||
|
@post.acting_user.staff? ||
|
||||||
|
@post.acting_user.trust_level >= TrustLevel[min_trust_level]
|
||||||
|
)
|
||||||
|
) || @post.topic&.pm_with_non_human_user?
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
@post.errors.add(:base, I18n.t("poll.insufficient_rights_to_create"))
|
@post.errors.add(:base, I18n.t("poll.insufficient_rights_to_create"))
|
||||||
|
|
|
@ -28,57 +28,63 @@ end
|
||||||
desc "Migrate old polls to new syntax"
|
desc "Migrate old polls to new syntax"
|
||||||
task "poll:migrate_old_polls" => :environment do
|
task "poll:migrate_old_polls" => :environment do
|
||||||
# iterate over all polls
|
# iterate over all polls
|
||||||
PluginStoreRow.where(plugin_name: "poll")
|
PluginStoreRow
|
||||||
|
.where(plugin_name: "poll")
|
||||||
.where("key LIKE 'poll_options_%'")
|
.where("key LIKE 'poll_options_%'")
|
||||||
.pluck(:key)
|
.pluck(:key)
|
||||||
.each do |poll_options_key|
|
.each do |poll_options_key|
|
||||||
# extract the post_id
|
# extract the post_id
|
||||||
post_id = poll_options_key["poll_options_".length..-1].to_i
|
post_id = poll_options_key["poll_options_".length..-1].to_i
|
||||||
# load the post from the db
|
# load the post from the db
|
||||||
if post = Post.find_by(id: post_id)
|
if post = Post.find_by(id: post_id)
|
||||||
putc "."
|
putc "."
|
||||||
# skip if already migrated
|
# skip if already migrated
|
||||||
next if post.custom_fields.include?("polls")
|
next if post.custom_fields.include?("polls")
|
||||||
# go back in time
|
# go back in time
|
||||||
freeze_time(post.created_at + 1.minute) do
|
freeze_time(post.created_at + 1.minute) do
|
||||||
raw = post.raw.gsub(/\n\n([ ]*[-\*\+] )/, "\n\\1") + "\n\n"
|
raw = post.raw.gsub(/\n\n([ ]*[-\*\+] )/, "\n\\1") + "\n\n"
|
||||||
# fix the RAW when needed
|
# fix the RAW when needed
|
||||||
if raw !~ /\[poll\]/
|
if raw !~ /\[poll\]/
|
||||||
lists = /^[ ]*[-\*\+] .+?$\n\n/m.match(raw)
|
lists = /^[ ]*[-\*\+] .+?$\n\n/m.match(raw)
|
||||||
next if lists.blank? || lists.length == 0
|
next if lists.blank? || lists.length == 0
|
||||||
first_list = lists[0]
|
first_list = lists[0]
|
||||||
raw = raw.sub(first_list, "\n[poll]\n#{first_list}\n[/poll]\n")
|
raw = raw.sub(first_list, "\n[poll]\n#{first_list}\n[/poll]\n")
|
||||||
end
|
end
|
||||||
# save the poll
|
# save the poll
|
||||||
post.raw = raw
|
post.raw = raw
|
||||||
post.save
|
post.save
|
||||||
# make sure we have a poll
|
# make sure we have a poll
|
||||||
next if post.custom_fields.blank? || !post.custom_fields.include?("polls")
|
next if post.custom_fields.blank? || !post.custom_fields.include?("polls")
|
||||||
# retrieve the new options
|
# retrieve the new options
|
||||||
options = post.custom_fields["polls"]["poll"]["options"]
|
options = post.custom_fields["polls"]["poll"]["options"]
|
||||||
# iterate over all votes
|
# iterate over all votes
|
||||||
PluginStoreRow.where(plugin_name: "poll")
|
PluginStoreRow
|
||||||
.where("key LIKE ?", "poll_vote_#{post_id}_%")
|
.where(plugin_name: "poll")
|
||||||
.pluck(:key, :value)
|
.where("key LIKE ?", "poll_vote_#{post_id}_%")
|
||||||
.each do |poll_vote_key, vote|
|
.pluck(:key, :value)
|
||||||
# extract the user_id
|
.each do |poll_vote_key, vote|
|
||||||
user_id = poll_vote_key["poll_vote_#{post_id}_%".length..-1].to_i
|
# extract the user_id
|
||||||
# find the selected option
|
user_id = poll_vote_key["poll_vote_#{post_id}_%".length..-1].to_i
|
||||||
vote = vote.strip
|
# find the selected option
|
||||||
selected_option = options.detect { |o| o["html"].strip === vote }
|
vote = vote.strip
|
||||||
# make sure we have a match
|
selected_option = options.detect { |o| o["html"].strip === vote }
|
||||||
next if selected_option.blank?
|
# make sure we have a match
|
||||||
# submit vote
|
next if selected_option.blank?
|
||||||
DiscoursePoll::Poll.vote(post_id, "poll", [selected_option["id"]], user_id) rescue nil
|
# submit vote
|
||||||
end
|
begin
|
||||||
# close the poll
|
DiscoursePoll::Poll.vote(post_id, "poll", [selected_option["id"]], user_id)
|
||||||
if post.topic.archived? || post.topic.closed? || poll_was_closed?(post.topic.title)
|
rescue StandardError
|
||||||
post.custom_fields["polls"]["poll"]["status"] = "closed"
|
nil
|
||||||
post.save_custom_fields(true)
|
end
|
||||||
|
end
|
||||||
|
# close the poll
|
||||||
|
if post.topic.archived? || post.topic.closed? || poll_was_closed?(post.topic.title)
|
||||||
|
post.custom_fields["polls"]["poll"]["status"] = "closed"
|
||||||
|
post.save_custom_fields(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
puts "", "Done!"
|
puts "", "Done!"
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,8 @@ after_initialize do
|
||||||
isolate_namespace DiscoursePoll
|
isolate_namespace DiscoursePoll
|
||||||
end
|
end
|
||||||
|
|
||||||
class Error < StandardError; end
|
class Error < StandardError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require_relative "app/controllers/polls_controller.rb"
|
require_relative "app/controllers/polls_controller.rb"
|
||||||
|
@ -49,13 +50,11 @@ after_initialize do
|
||||||
put "/vote" => "polls#vote"
|
put "/vote" => "polls#vote"
|
||||||
delete "/vote" => "polls#remove_vote"
|
delete "/vote" => "polls#remove_vote"
|
||||||
put "/toggle_status" => "polls#toggle_status"
|
put "/toggle_status" => "polls#toggle_status"
|
||||||
get "/voters" => 'polls#voters'
|
get "/voters" => "polls#voters"
|
||||||
get "/grouped_poll_results" => 'polls#grouped_poll_results'
|
get "/grouped_poll_results" => "polls#grouped_poll_results"
|
||||||
end
|
end
|
||||||
|
|
||||||
Discourse::Application.routes.append do
|
Discourse::Application.routes.append { mount ::DiscoursePoll::Engine, at: "/polls" }
|
||||||
mount ::DiscoursePoll::Engine, at: "/polls"
|
|
||||||
end
|
|
||||||
|
|
||||||
allow_new_queued_post_payload_attribute("is_poll")
|
allow_new_queued_post_payload_attribute("is_poll")
|
||||||
register_post_custom_field_type(DiscoursePoll::HAS_POLLS, :boolean)
|
register_post_custom_field_type(DiscoursePoll::HAS_POLLS, :boolean)
|
||||||
|
@ -74,18 +73,14 @@ after_initialize do
|
||||||
post = self
|
post = self
|
||||||
|
|
||||||
Poll.transaction do
|
Poll.transaction do
|
||||||
polls.values.each do |poll|
|
polls.values.each { |poll| DiscoursePoll::Poll.create!(post.id, poll) }
|
||||||
DiscoursePoll::Poll.create!(post.id, poll)
|
|
||||||
end
|
|
||||||
post.custom_fields[DiscoursePoll::HAS_POLLS] = true
|
post.custom_fields[DiscoursePoll::HAS_POLLS] = true
|
||||||
post.save_custom_fields(true)
|
post.save_custom_fields(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
User.class_eval do
|
User.class_eval { has_many :poll_votes, dependent: :delete_all }
|
||||||
has_many :poll_votes, dependent: :delete_all
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
validate(:post, :validate_polls) do |force = nil|
|
validate(:post, :validate_polls) do |force = nil|
|
||||||
|
@ -115,9 +110,7 @@ after_initialize do
|
||||||
if !DiscoursePoll::PollsValidator.new(post).validate_polls
|
if !DiscoursePoll::PollsValidator.new(post).validate_polls
|
||||||
result = NewPostResult.new(:poll, false)
|
result = NewPostResult.new(:poll, false)
|
||||||
|
|
||||||
post.errors.full_messages.each do |message|
|
post.errors.full_messages.each { |message| result.add_error(message) }
|
||||||
result.add_error(message)
|
|
||||||
end
|
|
||||||
|
|
||||||
result
|
result
|
||||||
else
|
else
|
||||||
|
@ -127,9 +120,7 @@ after_initialize do
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:approved_post) do |queued_post, created_post|
|
on(:approved_post) do |queued_post, created_post|
|
||||||
if queued_post.payload["is_poll"]
|
created_post.validate_polls(true) if queued_post.payload["is_poll"]
|
||||||
created_post.validate_polls(true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:reduce_cooked) do |fragment, post|
|
on(:reduce_cooked) do |fragment, post|
|
||||||
|
@ -137,22 +128,27 @@ after_initialize do
|
||||||
fragment.css(".poll, [data-poll-name]").each(&:remove)
|
fragment.css(".poll, [data-poll-name]").each(&:remove)
|
||||||
else
|
else
|
||||||
post_url = post.full_url
|
post_url = post.full_url
|
||||||
fragment.css(".poll, [data-poll-name]").each do |poll|
|
fragment
|
||||||
poll.replace "<p><a href='#{post_url}'>#{I18n.t("poll.email.link_to_poll")}</a></p>"
|
.css(".poll, [data-poll-name]")
|
||||||
end
|
.each do |poll|
|
||||||
|
poll.replace "<p><a href='#{post_url}'>#{I18n.t("poll.email.link_to_poll")}</a></p>"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:reduce_excerpt) do |doc, options|
|
on(:reduce_excerpt) do |doc, options|
|
||||||
post = options[:post]
|
post = options[:post]
|
||||||
|
|
||||||
replacement = post&.url.present? ?
|
replacement =
|
||||||
"<a href='#{UrlHelper.normalized_encode(post.url)}'>#{I18n.t("poll.poll")}</a>" :
|
(
|
||||||
I18n.t("poll.poll")
|
if post&.url.present?
|
||||||
|
"<a href='#{UrlHelper.normalized_encode(post.url)}'>#{I18n.t("poll.poll")}</a>"
|
||||||
|
else
|
||||||
|
I18n.t("poll.poll")
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
doc.css("div.poll").each do |poll|
|
doc.css("div.poll").each { |poll| poll.replace(replacement) }
|
||||||
poll.replace(replacement)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:post_created) do |post, _opts, user|
|
on(:post_created) do |post, _opts, user|
|
||||||
|
@ -162,7 +158,13 @@ after_initialize do
|
||||||
next if post.is_first_post?
|
next if post.is_first_post?
|
||||||
next if post.custom_fields[DiscoursePoll::HAS_POLLS].blank?
|
next if post.custom_fields[DiscoursePoll::HAS_POLLS].blank?
|
||||||
|
|
||||||
polls = ActiveModel::ArraySerializer.new(post.polls, each_serializer: PollSerializer, root: false, scope: guardian).as_json
|
polls =
|
||||||
|
ActiveModel::ArraySerializer.new(
|
||||||
|
post.polls,
|
||||||
|
each_serializer: PollSerializer,
|
||||||
|
root: false,
|
||||||
|
scope: guardian,
|
||||||
|
).as_json
|
||||||
post.publish_message!("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
post.publish_message!("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -171,63 +173,62 @@ after_initialize do
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_class(:topic_view, :polls) do
|
add_to_class(:topic_view, :polls) do
|
||||||
@polls ||= begin
|
@polls ||=
|
||||||
polls = {}
|
begin
|
||||||
|
polls = {}
|
||||||
|
|
||||||
post_with_polls = @post_custom_fields.each_with_object([]) do |fields, obj|
|
post_with_polls =
|
||||||
obj << fields[0] if fields[1][DiscoursePoll::HAS_POLLS]
|
@post_custom_fields.each_with_object([]) do |fields, obj|
|
||||||
end
|
obj << fields[0] if fields[1][DiscoursePoll::HAS_POLLS]
|
||||||
|
|
||||||
if post_with_polls.present?
|
|
||||||
Poll
|
|
||||||
.where(post_id: post_with_polls)
|
|
||||||
.each do |p|
|
|
||||||
polls[p.post_id] ||= []
|
|
||||||
polls[p.post_id] << p
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
polls
|
if post_with_polls.present?
|
||||||
end
|
Poll
|
||||||
|
.where(post_id: post_with_polls)
|
||||||
|
.each do |p|
|
||||||
|
polls[p.post_id] ||= []
|
||||||
|
polls[p.post_id] << p
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
polls
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:post, :preloaded_polls, false) do
|
add_to_serializer(:post, :preloaded_polls, false) do
|
||||||
@preloaded_polls ||= if @topic_view.present?
|
@preloaded_polls ||=
|
||||||
@topic_view.polls[object.id]
|
if @topic_view.present?
|
||||||
else
|
@topic_view.polls[object.id]
|
||||||
Poll.includes(:poll_options).where(post: object)
|
else
|
||||||
end
|
Poll.includes(:poll_options).where(post: object)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:post, :include_preloaded_polls?) do
|
add_to_serializer(:post, :include_preloaded_polls?) { false }
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
add_to_serializer(:post, :polls, false) do
|
add_to_serializer(:post, :polls, false) do
|
||||||
preloaded_polls.map { |p| PollSerializer.new(p, root: false, scope: self.scope) }
|
preloaded_polls.map { |p| PollSerializer.new(p, root: false, scope: self.scope) }
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:post, :include_polls?) do
|
add_to_serializer(:post, :include_polls?) { SiteSetting.poll_enabled && preloaded_polls.present? }
|
||||||
SiteSetting.poll_enabled && preloaded_polls.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
add_to_serializer(:post, :polls_votes, false) do
|
add_to_serializer(:post, :polls_votes, false) do
|
||||||
preloaded_polls.map do |poll|
|
preloaded_polls
|
||||||
user_poll_votes =
|
.map do |poll|
|
||||||
poll
|
user_poll_votes =
|
||||||
.poll_votes
|
poll
|
||||||
.where(user_id: scope.user.id)
|
.poll_votes
|
||||||
.joins(:poll_option)
|
.where(user_id: scope.user.id)
|
||||||
.pluck("poll_options.digest")
|
.joins(:poll_option)
|
||||||
|
.pluck("poll_options.digest")
|
||||||
|
|
||||||
[poll.name, user_poll_votes]
|
[poll.name, user_poll_votes]
|
||||||
end.to_h
|
end
|
||||||
|
.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:post, :include_polls_votes?) do
|
add_to_serializer(:post, :include_polls_votes?) do
|
||||||
SiteSetting.poll_enabled &&
|
SiteSetting.poll_enabled && scope.user&.id.present? && preloaded_polls.present? &&
|
||||||
scope.user&.id.present? &&
|
preloaded_polls.any? { |p| p.has_voted?(scope.user) }
|
||||||
preloaded_polls.present? &&
|
|
||||||
preloaded_polls.any? { |p| p.has_voted?(scope.user) }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,20 +7,48 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
|
|
||||||
let!(:user) { log_in }
|
let!(:user) { log_in }
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:poll) { Fabricate(:post, topic: topic, user: user, raw: "[poll]\n- A\n- B\n[/poll]") }
|
let(:poll) { Fabricate(:post, topic: topic, user: user, raw: "[poll]\n- A\n- B\n[/poll]") }
|
||||||
let(:multi_poll) { Fabricate(:post, topic: topic, user: user, raw: "[poll min=1 max=2 type=multiple public=true]\n- A\n- B\n[/poll]") }
|
let(:multi_poll) do
|
||||||
let(:public_poll_on_vote) { Fabricate(:post, topic: topic, user: user, raw: "[poll public=true results=on_vote]\n- A\n- B\n[/poll]") }
|
Fabricate(
|
||||||
let(:public_poll_on_close) { Fabricate(:post, topic: topic, user: user, raw: "[poll public=true results=on_close]\n- A\n- B\n[/poll]") }
|
:post,
|
||||||
|
topic: topic,
|
||||||
|
user: user,
|
||||||
|
raw: "[poll min=1 max=2 type=multiple public=true]\n- A\n- B\n[/poll]",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let(:public_poll_on_vote) do
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
topic: topic,
|
||||||
|
user: user,
|
||||||
|
raw: "[poll public=true results=on_vote]\n- A\n- B\n[/poll]",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let(:public_poll_on_close) do
|
||||||
|
Fabricate(
|
||||||
|
:post,
|
||||||
|
topic: topic,
|
||||||
|
user: user,
|
||||||
|
raw: "[poll public=true results=on_close]\n- A\n- B\n[/poll]",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
describe "#vote" do
|
describe "#vote" do
|
||||||
it "works" do
|
it "works" do
|
||||||
channel = "/polls/#{poll.topic_id}"
|
channel = "/polls/#{poll.topic_id}"
|
||||||
|
|
||||||
message = MessageBus.track_publish(channel) do
|
message =
|
||||||
put :vote, params: {
|
MessageBus
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
.track_publish(channel) do
|
||||||
}, format: :json
|
put :vote,
|
||||||
end.first
|
params: {
|
||||||
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
end
|
||||||
|
.first
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -36,19 +64,30 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
|
|
||||||
it "works in PM" do
|
it "works in PM" do
|
||||||
user2 = Fabricate(:user)
|
user2 = Fabricate(:user)
|
||||||
topic = Fabricate(:private_message_topic, topic_allowed_users: [
|
topic =
|
||||||
Fabricate.build(:topic_allowed_user, user: user),
|
Fabricate(
|
||||||
Fabricate.build(:topic_allowed_user, user: user2)
|
:private_message_topic,
|
||||||
])
|
topic_allowed_users: [
|
||||||
|
Fabricate.build(:topic_allowed_user, user: user),
|
||||||
|
Fabricate.build(:topic_allowed_user, user: user2),
|
||||||
|
],
|
||||||
|
)
|
||||||
poll = Fabricate(:post, topic: topic, user: user, raw: "[poll]\n- A\n- B\n[/poll]")
|
poll = Fabricate(:post, topic: topic, user: user, raw: "[poll]\n- A\n- B\n[/poll]")
|
||||||
|
|
||||||
channel = "/polls/#{poll.topic_id}"
|
channel = "/polls/#{poll.topic_id}"
|
||||||
|
|
||||||
message = MessageBus.track_publish(channel) do
|
message =
|
||||||
put :vote, params: {
|
MessageBus
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
.track_publish(channel) do
|
||||||
}, format: :json
|
put :vote,
|
||||||
end.first
|
params: {
|
||||||
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
end
|
||||||
|
.first
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -71,11 +110,18 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
|
|
||||||
channel = "/polls/#{poll.topic_id}"
|
channel = "/polls/#{poll.topic_id}"
|
||||||
|
|
||||||
message = MessageBus.track_publish(channel) do
|
message =
|
||||||
put :vote, params: {
|
MessageBus
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
.track_publish(channel) do
|
||||||
}, format: :json
|
put :vote,
|
||||||
end.first
|
params: {
|
||||||
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
end
|
||||||
|
.first
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -90,9 +136,7 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "requires at least 1 valid option" do
|
it "requires at least 1 valid option" do
|
||||||
put :vote, params: {
|
put :vote, params: { post_id: poll.id, poll_name: "poll", options: %w[A B] }, format: :json
|
||||||
post_id: poll.id, poll_name: "poll", options: ["A", "B"]
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -100,15 +144,23 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "supports vote changes" do
|
it "supports vote changes" do
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: poll.id, poll_name: "poll", options: ["e89dec30bbd9bf50fabf6a05b4324edf"]
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["e89dec30bbd9bf50fabf6a05b4324edf"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -118,15 +170,17 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "supports removing votes" do
|
it "supports removing votes" do
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
delete :remove_vote, params: {
|
delete :remove_vote, params: { post_id: poll.id, poll_name: "poll" }, format: :json
|
||||||
post_id: poll.id, poll_name: "poll"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -138,9 +192,13 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "works on closed topics" do
|
it "works on closed topics" do
|
||||||
topic.update_attribute(:closed, true)
|
topic.update_attribute(:closed, true)
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
end
|
end
|
||||||
|
@ -148,9 +206,7 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "ensures topic is not archived" do
|
it "ensures topic is not archived" do
|
||||||
topic.update_attribute(:archived, true)
|
topic.update_attribute(:archived, true)
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote, params: { post_id: poll.id, poll_name: "poll", options: ["A"] }, format: :json
|
||||||
post_id: poll.id, poll_name: "poll", options: ["A"]
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -160,9 +216,7 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "ensures post is not trashed" do
|
it "ensures post is not trashed" do
|
||||||
poll.trash!
|
poll.trash!
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote, params: { post_id: poll.id, poll_name: "poll", options: ["A"] }, format: :json
|
||||||
post_id: poll.id, poll_name: "poll", options: ["A"]
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -172,9 +226,7 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "ensures user can post in topic" do
|
it "ensures user can post in topic" do
|
||||||
Guardian.any_instance.expects(:can_create_post?).returns(false)
|
Guardian.any_instance.expects(:can_create_post?).returns(false)
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote, params: { post_id: poll.id, poll_name: "poll", options: ["A"] }, format: :json
|
||||||
post_id: poll.id, poll_name: "poll", options: ["A"]
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -182,9 +234,7 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "checks the name of the poll" do
|
it "checks the name of the poll" do
|
||||||
put :vote, params: {
|
put :vote, params: { post_id: poll.id, poll_name: "foobar", options: ["A"] }, format: :json
|
||||||
post_id: poll.id, poll_name: "foobar", options: ["A"]
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -194,9 +244,13 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "ensures poll is open" do
|
it "ensures poll is open" do
|
||||||
closed_poll = create_post(raw: "[poll status=closed]\n- A\n- B\n[/poll]")
|
closed_poll = create_post(raw: "[poll status=closed]\n- A\n- B\n[/poll]")
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: closed_poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
params: {
|
||||||
}, format: :json
|
post_id: closed_poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -206,13 +260,19 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "ensures user has required trust level" do
|
it "ensures user has required trust level" do
|
||||||
poll = create_post(raw: "[poll groups=#{Fabricate(:group).name}]\n- A\n- B\n[/poll]")
|
poll = create_post(raw: "[poll groups=#{Fabricate(:group).name}]\n- A\n- B\n[/poll]")
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t("js.poll.results.groups.title", groups: poll.polls.first.groups))
|
expect(json["errors"][0]).to eq(
|
||||||
|
I18n.t("js.poll.results.groups.title", groups: poll.polls.first.groups),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't discard anonymous votes when someone votes" do
|
it "doesn't discard anonymous votes when someone votes" do
|
||||||
|
@ -221,9 +281,13 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
the_poll.poll_options[0].update_attribute(:anonymous_votes, 11)
|
the_poll.poll_options[0].update_attribute(:anonymous_votes, 11)
|
||||||
the_poll.poll_options[1].update_attribute(:anonymous_votes, 6)
|
the_poll.poll_options[1].update_attribute(:anonymous_votes, 6)
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: ["5c24fc1df56d764b550ceae1b9319125"],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -238,13 +302,20 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "works for OP" do
|
it "works for OP" do
|
||||||
channel = "/polls/#{poll.topic_id}"
|
channel = "/polls/#{poll.topic_id}"
|
||||||
|
|
||||||
message = MessageBus.track_publish(channel) do
|
message =
|
||||||
put :toggle_status, params: {
|
MessageBus
|
||||||
post_id: poll.id, poll_name: "poll", status: "closed"
|
.track_publish(channel) do
|
||||||
}, format: :json
|
put :toggle_status,
|
||||||
|
params: {
|
||||||
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
status: "closed",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
end.first
|
end
|
||||||
|
.first
|
||||||
|
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["poll"]["status"]).to eq("closed")
|
expect(json["poll"]["status"]).to eq("closed")
|
||||||
|
@ -256,13 +327,20 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
|
|
||||||
channel = "/polls/#{poll.topic_id}"
|
channel = "/polls/#{poll.topic_id}"
|
||||||
|
|
||||||
message = MessageBus.track_publish(channel) do
|
message =
|
||||||
put :toggle_status, params: {
|
MessageBus
|
||||||
post_id: poll.id, poll_name: "poll", status: "closed"
|
.track_publish(channel) do
|
||||||
}, format: :json
|
put :toggle_status,
|
||||||
|
params: {
|
||||||
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
status: "closed",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
end.first
|
end
|
||||||
|
.first
|
||||||
|
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["poll"]["status"]).to eq("closed")
|
expect(json["poll"]["status"]).to eq("closed")
|
||||||
|
@ -272,9 +350,13 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "ensures post is not trashed" do
|
it "ensures post is not trashed" do
|
||||||
poll.trash!
|
poll.trash!
|
||||||
|
|
||||||
put :toggle_status, params: {
|
put :toggle_status,
|
||||||
post_id: poll.id, poll_name: "poll", status: "closed"
|
params: {
|
||||||
}, format: :json
|
post_id: poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
status: "closed",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).not_to eq(200)
|
expect(response.status).not_to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -289,31 +371,41 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
it "correctly handles offset" do
|
it "correctly handles offset" do
|
||||||
user1 = log_in
|
user1 = log_in
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: multi_poll.id, poll_name: "poll", options: [first]
|
params: {
|
||||||
}, format: :json
|
post_id: multi_poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: [first],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
user2 = log_in
|
user2 = log_in
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: multi_poll.id, poll_name: "poll", options: [first]
|
params: {
|
||||||
}, format: :json
|
post_id: multi_poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: [first],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
user3 = log_in
|
user3 = log_in
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: multi_poll.id, poll_name: "poll", options: [first, second]
|
params: {
|
||||||
}, format: :json
|
post_id: multi_poll.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: [first, second],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
get :voters, params: {
|
get :voters, params: { poll_name: "poll", post_id: multi_poll.id, limit: 2 }, format: :json
|
||||||
poll_name: 'poll', post_id: multi_poll.id, limit: 2
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -325,15 +417,17 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "ensures voters can only be seen after casting a vote" do
|
it "ensures voters can only be seen after casting a vote" do
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: public_poll_on_vote.id, poll_name: "poll", options: [first]
|
params: {
|
||||||
}, format: :json
|
post_id: public_poll_on_vote.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: [first],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
get :voters, params: {
|
get :voters, params: { poll_name: "poll", post_id: public_poll_on_vote.id }, format: :json
|
||||||
poll_name: "poll", post_id: public_poll_on_vote.id
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -343,21 +437,21 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
|
|
||||||
_user2 = log_in
|
_user2 = log_in
|
||||||
|
|
||||||
get :voters, params: {
|
get :voters, params: { poll_name: "poll", post_id: public_poll_on_vote.id }, format: :json
|
||||||
poll_name: "poll", post_id: public_poll_on_vote.id
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
|
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: public_poll_on_vote.id, poll_name: "poll", options: [second]
|
params: {
|
||||||
}, format: :json
|
post_id: public_poll_on_vote.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: [second],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
get :voters, params: {
|
get :voters, params: { poll_name: "poll", post_id: public_poll_on_vote.id }, format: :json
|
||||||
poll_name: "poll", post_id: public_poll_on_vote.id
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -368,27 +462,31 @@ RSpec.describe ::DiscoursePoll::PollsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "ensures voters can only be seen when poll is closed" do
|
it "ensures voters can only be seen when poll is closed" do
|
||||||
put :vote, params: {
|
put :vote,
|
||||||
post_id: public_poll_on_close.id, poll_name: "poll", options: [first]
|
params: {
|
||||||
}, format: :json
|
post_id: public_poll_on_close.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
options: [first],
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
get :voters, params: {
|
get :voters, params: { poll_name: "poll", post_id: public_poll_on_close.id }, format: :json
|
||||||
poll_name: "poll", post_id: public_poll_on_close.id
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
|
|
||||||
put :toggle_status, params: {
|
put :toggle_status,
|
||||||
post_id: public_poll_on_close.id, poll_name: "poll", status: "closed"
|
params: {
|
||||||
}, format: :json
|
post_id: public_poll_on_close.id,
|
||||||
|
poll_name: "poll",
|
||||||
|
status: "closed",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
get :voters, params: {
|
get :voters, params: { poll_name: "poll", post_id: public_poll_on_close.id }, format: :json
|
||||||
poll_name: "poll", post_id: public_poll_on_close.id
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,11 @@ RSpec.describe PostsController do
|
||||||
let!(:user) { log_in }
|
let!(:user) { log_in }
|
||||||
let!(:title) { "Testing Poll Plugin" }
|
let!(:title) { "Testing Poll Plugin" }
|
||||||
|
|
||||||
before do
|
before { SiteSetting.min_first_post_typing_time = 0 }
|
||||||
SiteSetting.min_first_post_typing_time = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "polls" do
|
describe "polls" do
|
||||||
it "works" do
|
it "works" do
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -25,9 +21,12 @@ RSpec.describe PostsController do
|
||||||
it "works on any post" do
|
it "works on any post" do
|
||||||
post_1 = Fabricate(:post)
|
post_1 = Fabricate(:post)
|
||||||
|
|
||||||
post :create, params: {
|
post :create,
|
||||||
topic_id: post_1.topic.id, raw: "[poll]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
topic_id: post_1.topic.id,
|
||||||
|
raw: "[poll]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -41,12 +40,13 @@ RSpec.describe PostsController do
|
||||||
close_date = 1.month.from_now.round
|
close_date = 1.month.from_now.round
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title,
|
params: {
|
||||||
raw: "[poll name=#{name} close=#{close_date.iso8601}]\n- A\n- B\n[/poll]"
|
title: title,
|
||||||
}, format: :json
|
raw: "[poll name=#{name} close=#{close_date.iso8601}]\n- A\n- B\n[/poll]",
|
||||||
end.to change { Jobs::ClosePoll.jobs.size }.by(1) &
|
},
|
||||||
change { Poll.count }.by(1)
|
format: :json
|
||||||
|
end.to change { Jobs::ClosePoll.jobs.size }.by(1) & change { Poll.count }.by(1)
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -62,9 +62,7 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have different options" do
|
it "should have different options" do
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- A\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- A\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -72,19 +70,20 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "accepts different Chinese options" do
|
it "accepts different Chinese options" do
|
||||||
SiteSetting.default_locale = 'zh_CN'
|
SiteSetting.default_locale = "zh_CN"
|
||||||
|
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll]\n- Microsoft Edge(新)\n- Microsoft Edge(旧)\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll]\n- Microsoft Edge(新)\n- Microsoft Edge(旧)\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response).to be_successful
|
expect(response).to be_successful
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have at least 1 options" do
|
it "should have at least 1 options" do
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -96,41 +95,54 @@ RSpec.describe PostsController do
|
||||||
(SiteSetting.poll_maximum_options + 1).times { |n| raw << "\n- #{n}" }
|
(SiteSetting.poll_maximum_options + 1).times { |n| raw << "\n- #{n}" }
|
||||||
raw << "\n[/poll]"
|
raw << "\n[/poll]"
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: raw }, format: :json
|
||||||
title: title, raw: raw
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options))
|
expect(json["errors"][0]).to eq(
|
||||||
|
I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have valid parameters" do
|
it "should have valid parameters" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll type=multiple min=5]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll type=multiple min=5]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"))
|
expect(json["errors"][0]).to eq(
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "prevents self-xss" do
|
it "prevents self-xss" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll name=<script>alert('xss')</script>]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll name=<script>alert('xss')</script>]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["cooked"]).to match("data-poll-")
|
expect(json["cooked"]).to match("data-poll-")
|
||||||
expect(json["cooked"]).to include("<script>")
|
expect(json["cooked"]).to include("<script>")
|
||||||
expect(Poll.find_by(post_id: json["id"]).name).to eq("<script>alert('xss')</script>")
|
expect(Poll.find_by(post_id: json["id"]).name).to eq(
|
||||||
|
"<script>alert('xss')</script>",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "also works when there is a link starting with '[poll'" do
|
it "also works when there is a link starting with '[poll'" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[Polls are awesome](/foobar)\n[poll]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[Polls are awesome](/foobar)\n[poll]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -139,9 +151,12 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "prevents poll-inception" do
|
it "prevents poll-inception" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll name=1]\n- A\n[poll name=2]\n- B\n- C\n[/poll]\n- D\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll name=1]\n- A\n[poll name=2]\n- B\n- C\n[/poll]\n- D\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -150,9 +165,12 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "accepts polls with titles" do
|
it "accepts polls with titles" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll]\n# What's up?\n- one\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll]\n# What's up?\n- one\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response).to be_successful
|
expect(response).to be_successful
|
||||||
poll = Poll.last
|
poll = Poll.last
|
||||||
|
@ -161,23 +179,24 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "edit window" do
|
describe "edit window" do
|
||||||
|
|
||||||
describe "within the first 5 minutes" do
|
describe "within the first 5 minutes" do
|
||||||
|
|
||||||
let(:post_id) do
|
let(:post_id) do
|
||||||
freeze_time(4.minutes.ago) do
|
freeze_time(4.minutes.ago) do
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
response.parsed_body["id"]
|
response.parsed_body["id"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can be changed" do
|
it "can be changed" do
|
||||||
put :update, params: {
|
put :update,
|
||||||
id: post_id, post: { raw: "[poll]\n- A\n- B\n- C\n[/poll]" }
|
params: {
|
||||||
}, format: :json
|
id: post_id,
|
||||||
|
post: {
|
||||||
|
raw: "[poll]\n- A\n- B\n- C\n[/poll]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -187,28 +206,29 @@ RSpec.describe PostsController do
|
||||||
it "resets the votes" do
|
it "resets the votes" do
|
||||||
DiscoursePoll::Poll.vote(user, post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"])
|
DiscoursePoll::Poll.vote(user, post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"])
|
||||||
|
|
||||||
put :update, params: {
|
put :update,
|
||||||
id: post_id, post: { raw: "[poll]\n- A\n- B\n- C\n[/poll]" }
|
params: {
|
||||||
}, format: :json
|
id: post_id,
|
||||||
|
post: {
|
||||||
|
raw: "[poll]\n- A\n- B\n- C\n[/poll]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["post"]["polls_votes"]).to_not be
|
expect(json["post"]["polls_votes"]).to_not be
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "after the poll edit window has expired" do
|
describe "after the poll edit window has expired" do
|
||||||
|
|
||||||
let(:poll) { "[poll]\n- A\n- B\n[/poll]" }
|
let(:poll) { "[poll]\n- A\n- B\n[/poll]" }
|
||||||
let(:new_option) { "[poll]\n- A\n- C\n[/poll]" }
|
let(:new_option) { "[poll]\n- A\n- C\n[/poll]" }
|
||||||
let(:updated) { "before\n\n[poll]\n- A\n- B\n[/poll]\n\nafter" }
|
let(:updated) { "before\n\n[poll]\n- A\n- B\n[/poll]\n\nafter" }
|
||||||
|
|
||||||
let(:post_id) do
|
let(:post_id) do
|
||||||
freeze_time(6.minutes.ago) do
|
freeze_time(6.minutes.ago) do
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: poll }, format: :json
|
||||||
title: title, raw: poll
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
response.parsed_body["id"]
|
response.parsed_body["id"]
|
||||||
end
|
end
|
||||||
|
@ -216,16 +236,11 @@ RSpec.describe PostsController do
|
||||||
|
|
||||||
let(:poll_edit_window_mins) { 6 }
|
let(:poll_edit_window_mins) { 6 }
|
||||||
|
|
||||||
before do
|
before { SiteSetting.poll_edit_window_mins = poll_edit_window_mins }
|
||||||
SiteSetting.poll_edit_window_mins = poll_edit_window_mins
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with no vote" do
|
describe "with no vote" do
|
||||||
|
|
||||||
it "can change the options" do
|
it "can change the options" do
|
||||||
put :update, params: {
|
put :update, params: { id: post_id, post: { raw: new_option } }, format: :json
|
||||||
id: post_id, post: { raw: new_option }
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -238,26 +253,24 @@ RSpec.describe PostsController do
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["post"]["cooked"]).to match("before")
|
expect(json["post"]["cooked"]).to match("before")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with at least one vote" do
|
describe "with at least one vote" do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
DiscoursePoll::Poll.vote(user, post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"])
|
DiscoursePoll::Poll.vote(user, post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "cannot change the options" do
|
it "cannot change the options" do
|
||||||
put :update, params: {
|
put :update, params: { id: post_id, post: { raw: new_option } }, format: :json
|
||||||
id: post_id, post: { raw: new_option }
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t(
|
expect(json["errors"][0]).to eq(
|
||||||
"poll.edit_window_expired.cannot_edit_default_poll_with_votes",
|
I18n.t(
|
||||||
minutes: poll_edit_window_mins
|
"poll.edit_window_expired.cannot_edit_default_poll_with_votes",
|
||||||
))
|
minutes: poll_edit_window_mins,
|
||||||
|
),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "support changes on the post" do
|
it "support changes on the post" do
|
||||||
|
@ -266,45 +279,49 @@ RSpec.describe PostsController do
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["post"]["cooked"]).to match("before")
|
expect(json["post"]["cooked"]).to match("before")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "named polls" do
|
describe "named polls" do
|
||||||
|
|
||||||
it "should have different options" do
|
it "should have different options" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll name=""foo""]\n- A\n- A\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw:
|
||||||
|
"[poll name=" \
|
||||||
|
"foo" \
|
||||||
|
"]\n- A\n- A\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t("poll.named_poll_must_have_different_options", name: "foo"))
|
expect(json["errors"][0]).to eq(
|
||||||
|
I18n.t("poll.named_poll_must_have_different_options", name: "foo"),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have at least 1 option" do
|
it "should have at least 1 option" do
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll name='foo']\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll name='foo']\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t("poll.named_poll_must_have_at_least_1_option", name: "foo"))
|
expect(json["errors"][0]).to eq(
|
||||||
|
I18n.t("poll.named_poll_must_have_at_least_1_option", name: "foo"),
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "multiple polls" do
|
describe "multiple polls" do
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -313,9 +330,12 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have a name" do
|
it "should have a name" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]\n[poll]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll]\n- A\n- B\n[/poll]\n[poll]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -323,46 +343,42 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have unique name" do
|
it "should have unique name" do
|
||||||
post :create, params: {
|
post :create,
|
||||||
title: title, raw: "[poll name=foo]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]"
|
params: {
|
||||||
}, format: :json
|
title: title,
|
||||||
|
raw: "[poll name=foo]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]",
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["errors"][0]).to eq(I18n.t("poll.multiple_polls_with_same_name", name: "foo"))
|
expect(json["errors"][0]).to eq(I18n.t("poll.multiple_polls_with_same_name", name: "foo"))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "disabled polls" do
|
describe "disabled polls" do
|
||||||
before do
|
before { SiteSetting.poll_enabled = false }
|
||||||
SiteSetting.poll_enabled = false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn’t cook the poll" do
|
it "doesn’t cook the poll" do
|
||||||
log_in_user(Fabricate(:user, admin: true, trust_level: 4))
|
log_in_user(Fabricate(:user, admin: true, trust_level: 4))
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
expect(json["cooked"]).to eq("<p>[poll]</p>\n<ul>\n<li>A</li>\n<li>B<br>\n[/poll]</li>\n</ul>")
|
expect(json["cooked"]).to eq(
|
||||||
|
"<p>[poll]</p>\n<ul>\n<li>A</li>\n<li>B<br>\n[/poll]</li>\n</ul>",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "regular user with insufficient trust level" do
|
describe "regular user with insufficient trust level" do
|
||||||
before do
|
before { SiteSetting.poll_minimum_trust_level_to_create = 2 }
|
||||||
SiteSetting.poll_minimum_trust_level_to_create = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
it "invalidates the post" do
|
it "invalidates the post" do
|
||||||
log_in_user(Fabricate(:user, trust_level: 1))
|
log_in_user(Fabricate(:user, trust_level: 1))
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -371,33 +387,31 @@ RSpec.describe PostsController do
|
||||||
|
|
||||||
it "skips the check in PMs with bots" do
|
it "skips the check in PMs with bots" do
|
||||||
user = Fabricate(:user, trust_level: 1)
|
user = Fabricate(:user, trust_level: 1)
|
||||||
topic = Fabricate(:private_message_topic, topic_allowed_users: [
|
topic =
|
||||||
Fabricate.build(:topic_allowed_user, user: user),
|
Fabricate(
|
||||||
Fabricate.build(:topic_allowed_user, user: Discourse.system_user)
|
:private_message_topic,
|
||||||
])
|
topic_allowed_users: [
|
||||||
|
Fabricate.build(:topic_allowed_user, user: user),
|
||||||
|
Fabricate.build(:topic_allowed_user, user: Discourse.system_user),
|
||||||
|
],
|
||||||
|
)
|
||||||
Fabricate(:post, topic_id: topic.id, user_id: Discourse::SYSTEM_USER_ID)
|
Fabricate(:post, topic_id: topic.id, user_id: Discourse::SYSTEM_USER_ID)
|
||||||
|
|
||||||
log_in_user(user)
|
log_in_user(user)
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { topic_id: topic.id, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
topic_id: topic.id, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.parsed_body["errors"]).to eq(nil)
|
expect(response.parsed_body["errors"]).to eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "regular user with equal trust level" do
|
describe "regular user with equal trust level" do
|
||||||
before do
|
before { SiteSetting.poll_minimum_trust_level_to_create = 2 }
|
||||||
SiteSetting.poll_minimum_trust_level_to_create = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
it "validates the post" do
|
it "validates the post" do
|
||||||
log_in_user(Fabricate(:user, trust_level: 2))
|
log_in_user(Fabricate(:user, trust_level: 2))
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -407,16 +421,12 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "regular user with superior trust level" do
|
describe "regular user with superior trust level" do
|
||||||
before do
|
before { SiteSetting.poll_minimum_trust_level_to_create = 2 }
|
||||||
SiteSetting.poll_minimum_trust_level_to_create = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
it "validates the post" do
|
it "validates the post" do
|
||||||
log_in_user(Fabricate(:user, trust_level: 3))
|
log_in_user(Fabricate(:user, trust_level: 3))
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -426,16 +436,12 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "staff with insufficient trust level" do
|
describe "staff with insufficient trust level" do
|
||||||
before do
|
before { SiteSetting.poll_minimum_trust_level_to_create = 2 }
|
||||||
SiteSetting.poll_minimum_trust_level_to_create = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
it "validates the post" do
|
it "validates the post" do
|
||||||
log_in_user(Fabricate(:user, moderator: true, trust_level: 1))
|
log_in_user(Fabricate(:user, moderator: true, trust_level: 1))
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json
|
||||||
title: title, raw: "[poll]\n- A\n- B\n[/poll]"
|
|
||||||
}, format: :json
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
@ -445,9 +451,7 @@ RSpec.describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "staff editing posts of users with insufficient trust level" do
|
describe "staff editing posts of users with insufficient trust level" do
|
||||||
before do
|
before { SiteSetting.poll_minimum_trust_level_to_create = 2 }
|
||||||
SiteSetting.poll_minimum_trust_level_to_create = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
it "validates the post" do
|
it "validates the post" do
|
||||||
log_in_user(Fabricate(:user, trust_level: 1))
|
log_in_user(Fabricate(:user, trust_level: 1))
|
||||||
|
@ -459,9 +463,14 @@ RSpec.describe PostsController do
|
||||||
|
|
||||||
log_in_user(Fabricate(:admin))
|
log_in_user(Fabricate(:admin))
|
||||||
|
|
||||||
put :update, params: {
|
put :update,
|
||||||
id: post_id, post: { raw: "#{title}\n[poll]\n- A\n- B\n- C\n[/poll]" }
|
params: {
|
||||||
}, format: :json
|
id: post_id,
|
||||||
|
post: {
|
||||||
|
raw: "#{title}\n[poll]\n- A\n- B\n- C\n[/poll]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.parsed_body["post"]["polls"][0]["options"][2]["html"]).to eq("C")
|
expect(response.parsed_body["post"]["polls"][0]["options"][2]["html"]).to eq("C")
|
||||||
|
|
|
@ -7,31 +7,25 @@ RSpec.describe "DiscoursePoll endpoints" do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
fab!(:post) { Fabricate(:post, raw: "[poll public=true]\n- A\n- B\n[/poll]") }
|
fab!(:post) { Fabricate(:post, raw: "[poll public=true]\n- A\n- B\n[/poll]") }
|
||||||
|
|
||||||
fab!(:post_with_multiple_poll) do
|
fab!(:post_with_multiple_poll) { Fabricate(:post, raw: <<~SQL) }
|
||||||
Fabricate(:post, raw: <<~SQL)
|
|
||||||
[poll type=multiple public=true min=1 max=2]
|
[poll type=multiple public=true min=1 max=2]
|
||||||
- A
|
- A
|
||||||
- B
|
- B
|
||||||
- C
|
- C
|
||||||
[/poll]
|
[/poll]
|
||||||
SQL
|
SQL
|
||||||
end
|
|
||||||
|
|
||||||
let(:option_a) { "5c24fc1df56d764b550ceae1b9319125" }
|
let(:option_a) { "5c24fc1df56d764b550ceae1b9319125" }
|
||||||
let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" }
|
let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" }
|
||||||
|
|
||||||
it "should return the right response" do
|
it "should return the right response" do
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(user, post.id, DiscoursePoll::DEFAULT_POLL_NAME, [option_a])
|
||||||
user,
|
|
||||||
post.id,
|
|
||||||
DiscoursePoll::DEFAULT_POLL_NAME,
|
|
||||||
[option_a]
|
|
||||||
)
|
|
||||||
|
|
||||||
get "/polls/voters.json", params: {
|
get "/polls/voters.json",
|
||||||
post_id: post.id,
|
params: {
|
||||||
poll_name: DiscoursePoll::DEFAULT_POLL_NAME
|
post_id: post.id,
|
||||||
}
|
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -43,19 +37,20 @@ RSpec.describe "DiscoursePoll endpoints" do
|
||||||
expect(option.first["username"]).to eq(user.username)
|
expect(option.first["username"]).to eq(user.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should return the right response for a single option' do
|
it "should return the right response for a single option" do
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(
|
||||||
user,
|
user,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
DiscoursePoll::DEFAULT_POLL_NAME,
|
DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
[option_a, option_b]
|
[option_a, option_b],
|
||||||
)
|
)
|
||||||
|
|
||||||
get "/polls/voters.json", params: {
|
get "/polls/voters.json",
|
||||||
post_id: post_with_multiple_poll.id,
|
params: {
|
||||||
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
post_id: post_with_multiple_poll.id,
|
||||||
option_id: option_b
|
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
}
|
option_id: option_b,
|
||||||
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -70,56 +65,60 @@ RSpec.describe "DiscoursePoll endpoints" do
|
||||||
expect(option.first["username"]).to eq(user.username)
|
expect(option.first["username"]).to eq(user.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when post_id is blank' do
|
describe "when post_id is blank" do
|
||||||
it 'should raise the right error' do
|
it "should raise the right error" do
|
||||||
get "/polls/voters.json", params: { poll_name: DiscoursePoll::DEFAULT_POLL_NAME }
|
get "/polls/voters.json", params: { poll_name: DiscoursePoll::DEFAULT_POLL_NAME }
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when post_id is not valid' do
|
describe "when post_id is not valid" do
|
||||||
it 'should raise the right error' do
|
it "should raise the right error" do
|
||||||
get "/polls/voters.json", params: {
|
get "/polls/voters.json",
|
||||||
post_id: -1,
|
params: {
|
||||||
poll_name: DiscoursePoll::DEFAULT_POLL_NAME
|
post_id: -1,
|
||||||
}
|
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
|
}
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
expect(response.body).to include('post_id')
|
expect(response.body).to include("post_id")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when poll_name is blank' do
|
describe "when poll_name is blank" do
|
||||||
it 'should raise the right error' do
|
it "should raise the right error" do
|
||||||
get "/polls/voters.json", params: { post_id: post.id }
|
get "/polls/voters.json", params: { post_id: post.id }
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when poll_name is not valid' do
|
describe "when poll_name is not valid" do
|
||||||
it 'should raise the right error' do
|
it "should raise the right error" do
|
||||||
get "/polls/voters.json", params: { post_id: post.id, poll_name: 'wrongpoll' }
|
get "/polls/voters.json", params: { post_id: post.id, poll_name: "wrongpoll" }
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
expect(response.body).to include('poll_name')
|
expect(response.body).to include("poll_name")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with number poll" do
|
context "with number poll" do
|
||||||
let(:post) { Fabricate(:post, raw: "[poll type=number min=1 max=20 step=1 public=true]\n[/poll]") }
|
let(:post) do
|
||||||
|
Fabricate(:post, raw: "[poll type=number min=1 max=20 step=1 public=true]\n[/poll]")
|
||||||
|
end
|
||||||
|
|
||||||
it 'should return the right response' do
|
it "should return the right response" do
|
||||||
post
|
post
|
||||||
|
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(
|
||||||
user,
|
user,
|
||||||
post.id,
|
post.id,
|
||||||
DiscoursePoll::DEFAULT_POLL_NAME,
|
DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
["4d8a15e3cc35750f016ce15a43937620"]
|
["4d8a15e3cc35750f016ce15a43937620"],
|
||||||
)
|
)
|
||||||
|
|
||||||
get "/polls/voters.json", params: {
|
get "/polls/voters.json",
|
||||||
post_id: post.id,
|
params: {
|
||||||
poll_name: DiscoursePoll::DEFAULT_POLL_NAME
|
post_id: post.id,
|
||||||
}
|
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
@ -137,31 +136,25 @@ RSpec.describe "DiscoursePoll endpoints" do
|
||||||
fab!(:user3) { Fabricate(:user) }
|
fab!(:user3) { Fabricate(:user) }
|
||||||
fab!(:user4) { Fabricate(:user) }
|
fab!(:user4) { Fabricate(:user) }
|
||||||
|
|
||||||
fab!(:post) do
|
fab!(:post) { Fabricate(:post, raw: <<~SQL) }
|
||||||
Fabricate(:post, raw: <<~SQL)
|
|
||||||
[poll type=multiple public=true min=1 max=2]
|
[poll type=multiple public=true min=1 max=2]
|
||||||
- A
|
- A
|
||||||
- B
|
- B
|
||||||
[/poll]
|
[/poll]
|
||||||
SQL
|
SQL
|
||||||
end
|
|
||||||
|
|
||||||
let(:option_a) { "5c24fc1df56d764b550ceae1b9319125" }
|
let(:option_a) { "5c24fc1df56d764b550ceae1b9319125" }
|
||||||
let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" }
|
let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
user_votes = {
|
user_votes = { user_0: option_a, user_1: option_a, user_2: option_b }
|
||||||
user_0: option_a,
|
|
||||||
user_1: option_a,
|
|
||||||
user_2: option_b,
|
|
||||||
}
|
|
||||||
|
|
||||||
[user1, user2, user3].each_with_index do |user, index|
|
[user1, user2, user3].each_with_index do |user, index|
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(
|
||||||
user,
|
user,
|
||||||
post.id,
|
post.id,
|
||||||
DiscoursePoll::DEFAULT_POLL_NAME,
|
DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
[user_votes["user_#{index}".to_sym]]
|
[user_votes["user_#{index}".to_sym]],
|
||||||
)
|
)
|
||||||
UserCustomField.create(user_id: user.id, name: "something", value: "value#{index}")
|
UserCustomField.create(user_id: user.id, name: "something", value: "value#{index}")
|
||||||
end
|
end
|
||||||
|
@ -171,7 +164,7 @@ RSpec.describe "DiscoursePoll endpoints" do
|
||||||
user4,
|
user4,
|
||||||
post.id,
|
post.id,
|
||||||
DiscoursePoll::DEFAULT_POLL_NAME,
|
DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
[option_a, option_b]
|
[option_a, option_b],
|
||||||
)
|
)
|
||||||
UserCustomField.create(user_id: user4.id, name: "something", value: "value1")
|
UserCustomField.create(user_id: user4.id, name: "something", value: "value1")
|
||||||
end
|
end
|
||||||
|
@ -179,32 +172,52 @@ RSpec.describe "DiscoursePoll endpoints" do
|
||||||
it "returns grouped poll results based on user field" do
|
it "returns grouped poll results based on user field" do
|
||||||
SiteSetting.poll_groupable_user_fields = "something"
|
SiteSetting.poll_groupable_user_fields = "something"
|
||||||
|
|
||||||
get "/polls/grouped_poll_results.json", params: {
|
get "/polls/grouped_poll_results.json",
|
||||||
post_id: post.id,
|
params: {
|
||||||
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
post_id: post.id,
|
||||||
user_field_name: "something"
|
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
}
|
user_field_name: "something",
|
||||||
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.parsed_body.deep_symbolize_keys).to eq(
|
expect(response.parsed_body.deep_symbolize_keys).to eq(
|
||||||
grouped_results: [
|
grouped_results: [
|
||||||
{ group: "Value0", options: [{ digest: option_a, html: "A", votes: 1 }, { digest: option_b, html: "B", votes: 0 }] },
|
{
|
||||||
{ group: "Value1", options: [{ digest: option_a, html: "A", votes: 2 }, { digest: option_b, html: "B", votes: 1 }] },
|
group: "Value0",
|
||||||
{ group: "Value2", options: [{ digest: option_a, html: "A", votes: 0 }, { digest: option_b, html: "B", votes: 1 }] },
|
options: [
|
||||||
]
|
{ digest: option_a, html: "A", votes: 1 },
|
||||||
|
{ digest: option_b, html: "B", votes: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "Value1",
|
||||||
|
options: [
|
||||||
|
{ digest: option_a, html: "A", votes: 2 },
|
||||||
|
{ digest: option_b, html: "B", votes: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "Value2",
|
||||||
|
options: [
|
||||||
|
{ digest: option_a, html: "A", votes: 0 },
|
||||||
|
{ digest: option_b, html: "B", votes: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns an error when poll_groupable_user_fields is empty" do
|
it "returns an error when poll_groupable_user_fields is empty" do
|
||||||
SiteSetting.poll_groupable_user_fields = ""
|
SiteSetting.poll_groupable_user_fields = ""
|
||||||
get "/polls/grouped_poll_results.json", params: {
|
get "/polls/grouped_poll_results.json",
|
||||||
post_id: post.id,
|
params: {
|
||||||
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
post_id: post.id,
|
||||||
user_field_name: "something"
|
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
|
||||||
}
|
user_field_name: "something",
|
||||||
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
expect(response.body).to include('user_field_name')
|
expect(response.body).to include("user_field_name")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,15 +5,17 @@ require "rails_helper"
|
||||||
RSpec.describe Jobs::ClosePoll do
|
RSpec.describe Jobs::ClosePoll do
|
||||||
let(:post) { Fabricate(:post, raw: "[poll]\n- A\n- B\n[/poll]") }
|
let(:post) { Fabricate(:post, raw: "[poll]\n- A\n- B\n[/poll]") }
|
||||||
|
|
||||||
describe 'missing arguments' do
|
describe "missing arguments" do
|
||||||
it 'should raise the right error' do
|
it "should raise the right error" do
|
||||||
expect do
|
expect do Jobs::ClosePoll.new.execute(post_id: post.id) end.to raise_error(
|
||||||
Jobs::ClosePoll.new.execute(post_id: post.id)
|
Discourse::InvalidParameters,
|
||||||
end.to raise_error(Discourse::InvalidParameters, "poll_name")
|
"poll_name",
|
||||||
|
)
|
||||||
|
|
||||||
expect do
|
expect do Jobs::ClosePoll.new.execute(poll_name: "poll") end.to raise_error(
|
||||||
Jobs::ClosePoll.new.execute(poll_name: "poll")
|
Discourse::InvalidParameters,
|
||||||
end.to raise_error(Discourse::InvalidParameters, "post_id")
|
"post_id",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,5 +26,4 @@ RSpec.describe Jobs::ClosePoll do
|
||||||
|
|
||||||
expect(post.polls.first.closed?).to eq(true)
|
expect(post.polls.first.closed?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,9 +7,7 @@ RSpec.describe NewPostManager do
|
||||||
let(:admin) { Fabricate(:admin) }
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
describe "when new post containing a poll is queued for approval" do
|
describe "when new post containing a poll is queued for approval" do
|
||||||
before do
|
before { SiteSetting.poll_minimum_trust_level_to_create = 0 }
|
||||||
SiteSetting.poll_minimum_trust_level_to_create = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
|
@ -23,9 +21,10 @@ RSpec.describe NewPostManager do
|
||||||
is_warning: false,
|
is_warning: false,
|
||||||
title: "This is a test post with a poll",
|
title: "This is a test post with a poll",
|
||||||
ip_address: "127.0.0.1",
|
ip_address: "127.0.0.1",
|
||||||
user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
user_agent:
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
referrer: "http://localhost:3000/",
|
referrer: "http://localhost:3000/",
|
||||||
first_post_checks: true
|
first_post_checks: true,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -38,7 +37,7 @@ RSpec.describe NewPostManager do
|
||||||
expect(Poll.where(post: review_result.created_post).exists?).to eq(true)
|
expect(Poll.where(post: review_result.created_post).exists?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 're-validates the poll when the approve_post event is triggered' do
|
it "re-validates the poll when the approve_post event is triggered" do
|
||||||
invalid_raw_poll = <<~MD
|
invalid_raw_poll = <<~MD
|
||||||
[poll type=multiple min=0]
|
[poll type=multiple min=0]
|
||||||
* 1
|
* 1
|
||||||
|
|
|
@ -4,17 +4,14 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
fab!(:user_2) { Fabricate(:user) }
|
fab!(:user_2) { Fabricate(:user) }
|
||||||
|
|
||||||
fab!(:post_with_regular_poll) do
|
fab!(:post_with_regular_poll) { Fabricate(:post, raw: <<~RAW) }
|
||||||
Fabricate(:post, raw: <<~RAW)
|
|
||||||
[poll]
|
[poll]
|
||||||
* 1
|
* 1
|
||||||
* 2
|
* 2
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
end
|
|
||||||
|
|
||||||
fab!(:post_with_multiple_poll) do
|
fab!(:post_with_multiple_poll) { Fabricate(:post, raw: <<~RAW) }
|
||||||
Fabricate(:post, raw: <<~RAW)
|
|
||||||
[poll type=multiple min=2 max=3]
|
[poll type=multiple min=2 max=3]
|
||||||
* 1
|
* 1
|
||||||
* 2
|
* 2
|
||||||
|
@ -23,10 +20,9 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
* 5
|
* 5
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
end
|
|
||||||
|
|
||||||
describe '.vote' do
|
describe ".vote" do
|
||||||
it 'should only allow one vote per user for a regular poll' do
|
it "should only allow one vote per user for a regular poll" do
|
||||||
poll = post_with_regular_poll.polls.first
|
poll = post_with_regular_poll.polls.first
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
|
@ -34,46 +30,35 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
user,
|
user,
|
||||||
post_with_regular_poll.id,
|
post_with_regular_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
poll.poll_options.map(&:digest)
|
poll.poll_options.map(&:digest),
|
||||||
)
|
)
|
||||||
end.to raise_error(DiscoursePoll::Error, I18n.t("poll.one_vote_per_user"))
|
end.to raise_error(DiscoursePoll::Error, I18n.t("poll.one_vote_per_user"))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should clean up bad votes for a regular poll' do
|
it "should clean up bad votes for a regular poll" do
|
||||||
poll = post_with_regular_poll.polls.first
|
poll = post_with_regular_poll.polls.first
|
||||||
|
|
||||||
PollVote.create!(
|
PollVote.create!(poll: poll, poll_option: poll.poll_options.first, user: user)
|
||||||
poll: poll,
|
|
||||||
poll_option: poll.poll_options.first,
|
|
||||||
user: user
|
|
||||||
)
|
|
||||||
|
|
||||||
PollVote.create!(
|
PollVote.create!(poll: poll, poll_option: poll.poll_options.last, user: user)
|
||||||
poll: poll,
|
|
||||||
poll_option: poll.poll_options.last,
|
|
||||||
user: user
|
|
||||||
)
|
|
||||||
|
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(
|
||||||
user,
|
user,
|
||||||
post_with_regular_poll.id,
|
post_with_regular_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
[poll.poll_options.first.digest]
|
[poll.poll_options.first.digest],
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id))
|
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly(
|
||||||
.to contain_exactly(poll.poll_options.first.id)
|
poll.poll_options.first.id,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows user to vote on multiple options correctly for a multiple poll' do
|
it "allows user to vote on multiple options correctly for a multiple poll" do
|
||||||
poll = post_with_multiple_poll.polls.first
|
poll = post_with_multiple_poll.polls.first
|
||||||
poll_options = poll.poll_options
|
poll_options = poll.poll_options
|
||||||
|
|
||||||
[
|
[poll_options.first, poll_options.second, poll_options.third].each do |poll_option|
|
||||||
poll_options.first,
|
|
||||||
poll_options.second,
|
|
||||||
poll_options.third,
|
|
||||||
].each do |poll_option|
|
|
||||||
PollVote.create!(poll: poll, poll_option: poll_option, user: user)
|
PollVote.create!(poll: poll, poll_option: poll_option, user: user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -81,24 +66,28 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
user,
|
user,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
[poll_options.first.digest, poll_options.second.digest]
|
[poll_options.first.digest, poll_options.second.digest],
|
||||||
)
|
)
|
||||||
|
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(
|
||||||
user_2,
|
user_2,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
[poll_options.third.digest, poll_options.fourth.digest]
|
[poll_options.third.digest, poll_options.fourth.digest],
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id))
|
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly(
|
||||||
.to contain_exactly(poll_options.first.id, poll_options.second.id)
|
poll_options.first.id,
|
||||||
|
poll_options.second.id,
|
||||||
|
)
|
||||||
|
|
||||||
expect(PollVote.where(poll: poll, user: user_2).pluck(:poll_option_id))
|
expect(PollVote.where(poll: poll, user: user_2).pluck(:poll_option_id)).to contain_exactly(
|
||||||
.to contain_exactly(poll_options.third.id, poll_options.fourth.id)
|
poll_options.third.id,
|
||||||
|
poll_options.fourth.id,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should respect the min/max votes per user for a multiple poll' do
|
it "should respect the min/max votes per user for a multiple poll" do
|
||||||
poll = post_with_multiple_poll.polls.first
|
poll = post_with_multiple_poll.polls.first
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
|
@ -106,27 +95,21 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
user,
|
user,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
poll.poll_options.map(&:digest)
|
poll.poll_options.map(&:digest),
|
||||||
)
|
)
|
||||||
end.to raise_error(
|
end.to raise_error(DiscoursePoll::Error, I18n.t("poll.max_vote_per_user", count: poll.max))
|
||||||
DiscoursePoll::Error,
|
|
||||||
I18n.t("poll.max_vote_per_user", count: poll.max)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
DiscoursePoll::Poll.vote(
|
DiscoursePoll::Poll.vote(
|
||||||
user,
|
user,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
[poll.poll_options.first.digest]
|
[poll.poll_options.first.digest],
|
||||||
)
|
)
|
||||||
end.to raise_error(
|
end.to raise_error(DiscoursePoll::Error, I18n.t("poll.min_vote_per_user", count: poll.min))
|
||||||
DiscoursePoll::Error,
|
|
||||||
I18n.t("poll.min_vote_per_user", count: poll.min)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should allow user to vote on a multiple poll even if min option is not configured' do
|
it "should allow user to vote on a multiple poll even if min option is not configured" do
|
||||||
post_with_multiple_poll = Fabricate(:post, raw: <<~RAW)
|
post_with_multiple_poll = Fabricate(:post, raw: <<~RAW)
|
||||||
[poll type=multiple max=3]
|
[poll type=multiple max=3]
|
||||||
* 1
|
* 1
|
||||||
|
@ -143,14 +126,15 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
user,
|
user,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
[poll.poll_options.first.digest]
|
[poll.poll_options.first.digest],
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id))
|
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly(
|
||||||
.to contain_exactly(poll.poll_options.first.id)
|
poll.poll_options.first.id,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should allow user to vote on a multiple poll even if max option is not configured' do
|
it "should allow user to vote on a multiple poll even if max option is not configured" do
|
||||||
post_with_multiple_poll = Fabricate(:post, raw: <<~RAW)
|
post_with_multiple_poll = Fabricate(:post, raw: <<~RAW)
|
||||||
[poll type=multiple min=1]
|
[poll type=multiple min=1]
|
||||||
* 1
|
* 1
|
||||||
|
@ -167,11 +151,13 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
user,
|
user,
|
||||||
post_with_multiple_poll.id,
|
post_with_multiple_poll.id,
|
||||||
"poll",
|
"poll",
|
||||||
[poll.poll_options.first.digest, poll.poll_options.second.digest]
|
[poll.poll_options.first.digest, poll.poll_options.second.digest],
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id))
|
expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly(
|
||||||
.to contain_exactly(poll.poll_options.first.id, poll.poll_options.second.id)
|
poll.poll_options.first.id,
|
||||||
|
poll.poll_options.second.id,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -179,19 +165,14 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
it "publishes on message bus if a there are polls" do
|
it "publishes on message bus if a there are polls" do
|
||||||
first_post = Fabricate(:post)
|
first_post = Fabricate(:post)
|
||||||
topic = first_post.topic
|
topic = first_post.topic
|
||||||
creator = PostCreator.new(user,
|
creator = PostCreator.new(user, topic_id: topic.id, raw: <<~RAW)
|
||||||
topic_id: topic.id,
|
|
||||||
raw: <<~RAW
|
|
||||||
[poll]
|
[poll]
|
||||||
* 1
|
* 1
|
||||||
* 2
|
* 2
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
)
|
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/polls/#{topic.id}") do
|
messages = MessageBus.track_publish("/polls/#{topic.id}") { creator.create! }
|
||||||
creator.create!
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(messages.count).to eq(1)
|
expect(messages.count).to eq(1)
|
||||||
end
|
end
|
||||||
|
@ -199,20 +180,16 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
it "does not publish on message bus when a post with no polls is created" do
|
it "does not publish on message bus when a post with no polls is created" do
|
||||||
first_post = Fabricate(:post)
|
first_post = Fabricate(:post)
|
||||||
topic = first_post.topic
|
topic = first_post.topic
|
||||||
creator = PostCreator.new(user,
|
creator =
|
||||||
topic_id: topic.id,
|
PostCreator.new(user, topic_id: topic.id, raw: "Just a post with definitely no polls")
|
||||||
raw: "Just a post with definitely no polls"
|
|
||||||
)
|
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/polls/#{topic.id}") do
|
messages = MessageBus.track_publish("/polls/#{topic.id}") { creator.create! }
|
||||||
creator.create!
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(messages.count).to eq(0)
|
expect(messages.count).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.extract' do
|
describe ".extract" do
|
||||||
it "skips the polls inside quote" do
|
it "skips the polls inside quote" do
|
||||||
raw = <<~RAW
|
raw = <<~RAW
|
||||||
[quote="username, post:1, topic:2"]
|
[quote="username, post:1, topic:2"]
|
||||||
|
@ -230,18 +207,17 @@ RSpec.describe DiscoursePoll::Poll do
|
||||||
Post with a poll and a quoted poll.
|
Post with a poll and a quoted poll.
|
||||||
RAW
|
RAW
|
||||||
|
|
||||||
expect(DiscoursePoll::Poll.extract(raw, 2)).to contain_exactly({
|
expect(DiscoursePoll::Poll.extract(raw, 2)).to contain_exactly(
|
||||||
"name" => "poll",
|
{
|
||||||
"options" => [{
|
"name" => "poll",
|
||||||
"html" => "3",
|
"options" => [
|
||||||
"id" => "68b434ff88aeae7054e42cd05a4d9056"
|
{ "html" => "3", "id" => "68b434ff88aeae7054e42cd05a4d9056" },
|
||||||
}, {
|
{ "html" => "4", "id" => "aa2393b424f2f395abb63bf785760a3b" },
|
||||||
"html" => "4",
|
],
|
||||||
"id" => "aa2393b424f2f395abb63bf785760a3b"
|
"status" => "open",
|
||||||
}],
|
"type" => "regular",
|
||||||
"status" => "open",
|
},
|
||||||
"type" => "regular"
|
)
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,68 +1,54 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe DiscoursePoll::PollsUpdater do
|
RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
|
|
||||||
def update(post, polls)
|
def update(post, polls)
|
||||||
DiscoursePoll::PollsUpdater.update(post, polls)
|
DiscoursePoll::PollsUpdater.update(post, polls)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
let(:post) {
|
let(:post) { Fabricate(:post, raw: <<~RAW) }
|
||||||
Fabricate(:post, raw: <<~RAW)
|
|
||||||
[poll]
|
[poll]
|
||||||
* 1
|
* 1
|
||||||
* 2
|
* 2
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
}
|
|
||||||
|
|
||||||
let(:post_with_3_options) {
|
let(:post_with_3_options) { Fabricate(:post, raw: <<~RAW) }
|
||||||
Fabricate(:post, raw: <<~RAW)
|
|
||||||
[poll]
|
[poll]
|
||||||
- a
|
- a
|
||||||
- b
|
- b
|
||||||
- c
|
- c
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
}
|
|
||||||
|
|
||||||
let(:post_with_some_attributes) {
|
let(:post_with_some_attributes) { Fabricate(:post, raw: <<~RAW) }
|
||||||
Fabricate(:post, raw: <<~RAW)
|
|
||||||
[poll close=#{1.week.from_now.to_formatted_s(:iso8601)} results=on_close]
|
[poll close=#{1.week.from_now.to_formatted_s(:iso8601)} results=on_close]
|
||||||
- A
|
- A
|
||||||
- B
|
- B
|
||||||
- C
|
- C
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
}
|
|
||||||
|
|
||||||
let(:polls) {
|
let(:polls) { DiscoursePoll::PollsValidator.new(post).validate_polls }
|
||||||
DiscoursePoll::PollsValidator.new(post).validate_polls
|
|
||||||
}
|
|
||||||
|
|
||||||
let(:polls_with_3_options) {
|
let(:polls_with_3_options) do
|
||||||
DiscoursePoll::PollsValidator.new(post_with_3_options).validate_polls
|
DiscoursePoll::PollsValidator.new(post_with_3_options).validate_polls
|
||||||
}
|
end
|
||||||
|
|
||||||
let(:polls_with_some_attributes) {
|
let(:polls_with_some_attributes) do
|
||||||
DiscoursePoll::PollsValidator.new(post_with_some_attributes).validate_polls
|
DiscoursePoll::PollsValidator.new(post_with_some_attributes).validate_polls
|
||||||
}
|
end
|
||||||
|
|
||||||
describe "update" do
|
describe "update" do
|
||||||
|
|
||||||
it "does nothing when there are no changes" do
|
it "does nothing when there are no changes" do
|
||||||
message = MessageBus.track_publish("/polls/#{post.topic_id}") do
|
message = MessageBus.track_publish("/polls/#{post.topic_id}") { update(post, polls) }.first
|
||||||
update(post, polls)
|
|
||||||
end.first
|
|
||||||
|
|
||||||
expect(message).to be(nil)
|
expect(message).to be(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "when editing" do
|
describe "when editing" do
|
||||||
|
let(:raw) { <<~RAW }
|
||||||
let(:raw) do
|
|
||||||
<<~RAW
|
|
||||||
This is a new poll with three options.
|
This is a new poll with three options.
|
||||||
|
|
||||||
[poll type=multiple results=always min=1 max=2]
|
[poll type=multiple results=always min=1 max=2]
|
||||||
|
@ -71,7 +57,6 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
* third
|
* third
|
||||||
[/poll]
|
[/poll]
|
||||||
RAW
|
RAW
|
||||||
end
|
|
||||||
|
|
||||||
let(:post) { Fabricate(:post, raw: raw) }
|
let(:post) { Fabricate(:post, raw: raw) }
|
||||||
|
|
||||||
|
@ -84,11 +69,9 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
|
|
||||||
expect(post.errors[:base].size).to equal(0)
|
expect(post.errors[:base].size).to equal(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "deletes polls" do
|
describe "deletes polls" do
|
||||||
|
|
||||||
it "that were removed" do
|
it "that were removed" do
|
||||||
update(post, {})
|
update(post, {})
|
||||||
|
|
||||||
|
@ -97,19 +80,15 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
expect(Poll.where(post: post).exists?).to eq(false)
|
expect(Poll.where(post: post).exists?).to eq(false)
|
||||||
expect(post.custom_fields[DiscoursePoll::HAS_POLLS]).to eq(nil)
|
expect(post.custom_fields[DiscoursePoll::HAS_POLLS]).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "creates polls" do
|
describe "creates polls" do
|
||||||
|
|
||||||
it "that were added" do
|
it "that were added" do
|
||||||
post = Fabricate(:post)
|
post = Fabricate(:post)
|
||||||
|
|
||||||
expect(Poll.find_by(post: post)).to_not be
|
expect(Poll.find_by(post: post)).to_not be
|
||||||
|
|
||||||
message = MessageBus.track_publish("/polls/#{post.topic_id}") do
|
message = MessageBus.track_publish("/polls/#{post.topic_id}") { update(post, polls) }.first
|
||||||
update(post, polls)
|
|
||||||
end.first
|
|
||||||
|
|
||||||
poll = Poll.find_by(post: post)
|
poll = Poll.find_by(post: post)
|
||||||
|
|
||||||
|
@ -121,21 +100,19 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
expect(message.data[:post_id]).to eq(post.id)
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
expect(message.data[:polls][0][:name]).to eq(poll.name)
|
expect(message.data[:polls][0][:name]).to eq(poll.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "updates polls" do
|
describe "updates polls" do
|
||||||
|
|
||||||
describe "when there are no votes" do
|
describe "when there are no votes" do
|
||||||
|
|
||||||
it "at any time" do
|
it "at any time" do
|
||||||
post # create the post
|
post # create the post
|
||||||
|
|
||||||
freeze_time 1.month.from_now
|
freeze_time 1.month.from_now
|
||||||
|
|
||||||
message = MessageBus.track_publish("/polls/#{post.topic_id}") do
|
message =
|
||||||
update(post, polls_with_some_attributes)
|
MessageBus
|
||||||
end.first
|
.track_publish("/polls/#{post.topic_id}") { update(post, polls_with_some_attributes) }
|
||||||
|
.first
|
||||||
|
|
||||||
poll = Poll.find_by(post: post)
|
poll = Poll.find_by(post: post)
|
||||||
|
|
||||||
|
@ -150,11 +127,9 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
expect(message.data[:post_id]).to eq(post.id)
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
expect(message.data[:polls][0][:name]).to eq(poll.name)
|
expect(message.data[:polls][0][:name]).to eq(poll.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "when there are votes" do
|
describe "when there are votes" do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
expect {
|
expect {
|
||||||
DiscoursePoll::Poll.vote(user, post.id, "poll", [polls["poll"]["options"][0]["id"]])
|
DiscoursePoll::Poll.vote(user, post.id, "poll", [polls["poll"]["options"][0]["id"]])
|
||||||
|
@ -162,11 +137,13 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "inside the edit window" do
|
describe "inside the edit window" do
|
||||||
|
|
||||||
it "and deletes the votes" do
|
it "and deletes the votes" do
|
||||||
message = MessageBus.track_publish("/polls/#{post.topic_id}") do
|
message =
|
||||||
update(post, polls_with_some_attributes)
|
MessageBus
|
||||||
end.first
|
.track_publish("/polls/#{post.topic_id}") do
|
||||||
|
update(post, polls_with_some_attributes)
|
||||||
|
end
|
||||||
|
.first
|
||||||
|
|
||||||
poll = Poll.find_by(post: post)
|
poll = Poll.find_by(post: post)
|
||||||
|
|
||||||
|
@ -181,11 +158,9 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
expect(message.data[:post_id]).to eq(post.id)
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
expect(message.data[:polls][0][:name]).to eq(poll.name)
|
expect(message.data[:polls][0][:name]).to eq(poll.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "outside the edit window" do
|
describe "outside the edit window" do
|
||||||
|
|
||||||
it "throws an error" do
|
it "throws an error" do
|
||||||
edit_window = SiteSetting.poll_edit_window_mins
|
edit_window = SiteSetting.poll_edit_window_mins
|
||||||
|
|
||||||
|
@ -204,17 +179,12 @@ RSpec.describe DiscoursePoll::PollsUpdater do
|
||||||
expect(post.errors[:base]).to include(
|
expect(post.errors[:base]).to include(
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"poll.edit_window_expired.cannot_edit_default_poll_with_votes",
|
"poll.edit_window_expired.cannot_edit_default_poll_with_votes",
|
||||||
minutes: edit_window
|
minutes: edit_window,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue