diff --git a/app/assets/javascripts/discourse/app/components/flag-action-type.js b/app/assets/javascripts/discourse/app/components/flag-action-type.js index 0041814118b..963742bf18e 100644 --- a/app/assets/javascripts/discourse/app/components/flag-action-type.js +++ b/app/assets/javascripts/discourse/app/components/flag-action-type.js @@ -14,7 +14,9 @@ export default Component.extend({ @discourseComputed("flag.name_key") customPlaceholder(nameKey) { - return I18n.t("flagging.custom_placeholder_" + nameKey); + return I18n.t("flagging.custom_placeholder_" + nameKey, { + defaultValue: I18n.t("flagging.custom_placeholder_notify_moderators"), + }); }, @discourseComputed("flag.name", "flag.name_key", "username") @@ -22,7 +24,9 @@ export default Component.extend({ if (["notify_user", "notify_moderators"].includes(nameKey)) { return name.replace(/{{username}}|%{username}/, username); } else { - return I18n.t("flagging.formatted_name." + nameKey); + return I18n.t("flagging.formatted_name." + nameKey, { + defaultValue: name, + }); } }, diff --git a/app/assets/javascripts/discourse/app/lib/transform-post.js b/app/assets/javascripts/discourse/app/lib/transform-post.js index 35db31b6feb..8c96353a63e 100644 --- a/app/assets/javascripts/discourse/app/lib/transform-post.js +++ b/app/assets/javascripts/discourse/app/lib/transform-post.js @@ -254,7 +254,11 @@ export default function transformPost( postId: post.id, action, canUndo: a.can_undo, - description: I18n.t(`post.actions.by_you.${action}`), + description: I18n.t(`post.actions.by_you.${action}`, { + defaultValue: I18n.t(`post.actions.by_you.custom`, { + custom: a.actionType.name, + }), + }), }; }); } diff --git a/app/models/flag.rb b/app/models/flag.rb new file mode 100644 index 00000000000..5085d0246a2 --- /dev/null +++ b/app/models/flag.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class Flag < ActiveRecord::Base + MAX_SYSTEM_FLAG_ID = 1000 + scope :enabled, -> { where(enabled: true) } + scope :system, -> { where("id < 1000") } + + before_save :set_position + before_save :set_name_key + after_save :reset_flag_settings! + after_destroy :reset_flag_settings! + + default_scope { order(:position) } + + def used? + PostAction.exists?(post_action_type_id: self.id) || + ReviewableScore.exists?(reviewable_score_type: self.id) + end + + def self.reset_flag_settings! + # Flags are memoized for better performance. After the update, we need to reload them in all processes. + PostActionType.reload_types + DiscourseEvent.trigger(:reload_post_action_types) + end + + def system? + self.id < MAX_SYSTEM_FLAG_ID + end + + def applies_to?(type) + self.applies_to.include?(type) + end + + private + + def reset_flag_settings! + self.class.reset_flag_settings! + end + + def set_position + self.position = Flag.maximum(:position).to_i + 1 if !self.position + end + + def set_name_key + self.name_key = self.name.squeeze(" ").gsub(" ", "_").gsub(/[^\w]/, "").downcase + end +end + +# == Schema Information +# +# Table name: flags +# +# id :bigint not null, primary key +# name :string +# name_key :string +# description :text +# notify_type :boolean default(FALSE), not null +# auto_action_type :boolean default(FALSE), not null +# custom_type :boolean default(FALSE), not null +# applies_to :string not null, is an Array +# position :integer not null +# enabled :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null +# diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb index 9b49575e3b7..0c596d9055e 100644 --- a/app/models/post_action_type.rb +++ b/app/models/post_action_type.rb @@ -11,39 +11,47 @@ class PostActionType < ActiveRecord::Base ApplicationSerializer.expire_cache_fragment!(/\Apost_action_flag_types_/) end + DiscourseEvent.on(:reload_post_action_types) { self.reload_types } + class << self attr_reader :flag_settings - def replace_flag_settings(settings) - if settings - @flag_settings = settings - else - initialize_flag_settings - end - @types = nil + def initialize_flag_settings + @flag_settings = FlagSettings.new end - def ordered - order("position asc") + def replace_flag_settings(settings) + Discourse.deprecate("Flags should not be replaced. Insert custom flags as database records.") + @flag_settings = settings || FlagSettings.new + @all_flags = nil end def types - unless @types - # NOTE: Previously bookmark was type 1 but that has been superseded - # by the separate Bookmark model and functionality - @types = Enum.new(like: 2) - @types.merge!(flag_settings.flag_types) - end + return Enum.new(like: 2).merge!(flag_settings.flag_types) if overridden_by_plugin? + Enum.new(like: 2).merge(flag_types) + end - @types + def reload_types + @all_flags = nil + @flag_settings = FlagSettings.new + ReviewableScore.reload_types + end + + def overridden_by_plugin? + flag_settings.flag_types.present? + end + + def all_flags + @all_flags ||= Flag.all end def auto_action_flag_types - flag_settings.auto_action_types + return flag_settings.auto_action_types if overridden_by_plugin? + flag_enum(all_flags.select(&:auto_action_type)) end def public_types - @public_types ||= types.except(*flag_types.keys << :notify_user) + types.except(*flag_types.keys << :notify_user) end def public_type_ids @@ -51,11 +59,13 @@ class PostActionType < ActiveRecord::Base end def flag_types_without_custom - flag_settings.without_custom_types + return flag_settings.without_custom_types if overridden_by_plugin? + flag_enum(all_flags.reject(&:custom_type)) end def flag_types - flag_settings.flag_types + return flag_settings.flag_types if overridden_by_plugin? + flag_enum(all_flags) end # flags resulting in mod notifications @@ -64,15 +74,25 @@ class PostActionType < ActiveRecord::Base end def notify_flag_types - flag_settings.notify_types + return flag_settings.notify_types if overridden_by_plugin? + flag_enum(all_flags.select(&:notify_type)) end def topic_flag_types - flag_settings.topic_flag_types + if overridden_by_plugin? + flag_settings.topic_flag_types + else + flag_enum(all_flags.select { |flag| flag.applies_to?("Topic") }) + end end def custom_types - flag_settings.custom_types + return flag_settings.custom_types if overridden_by_plugin? + flag_enum(all_flags.select(&:custom_type)) + end + + def names + all_flags.pluck(:id, :name).to_h end def is_flag?(sym) @@ -81,28 +101,8 @@ class PostActionType < ActiveRecord::Base private - def initialize_flag_settings - @flag_settings = FlagSettings.new - @flag_settings.add(3, :off_topic, notify_type: true, auto_action_type: true) - @flag_settings.add( - 4, - :inappropriate, - topic_type: true, - notify_type: true, - auto_action_type: true, - ) - @flag_settings.add(8, :spam, topic_type: true, notify_type: true, auto_action_type: true) - @flag_settings.add(6, :notify_user, topic_type: false, notify_type: false, custom_type: true) - @flag_settings.add( - 7, - :notify_moderators, - topic_type: true, - notify_type: true, - custom_type: true, - ) - @flag_settings.add(10, :illegal, topic_type: true, notify_type: true, custom_type: true) - # When adding a new ID here, check that it doesn't clash with any added in - # `ReviewableScore.types`. You can thank me later. + def flag_enum(scope) + Enum.new(scope.map { |flag| [flag.name_key.to_sym, flag.id] }.to_h) end end diff --git a/app/serializers/post_action_type_serializer.rb b/app/serializers/post_action_type_serializer.rb index c898b582a11..7f42c654e9a 100644 --- a/app/serializers/post_action_type_serializer.rb +++ b/app/serializers/post_action_type_serializer.rb @@ -14,25 +14,25 @@ class PostActionTypeSerializer < ApplicationSerializer end def name - i18n("title") + i18n("title", default: object.class.names[object.id]) end def description - i18n("description", tos_url: tos_url, base_path: Discourse.base_path) + i18n("description", vars: { tos_url:, base_path: Discourse.base_path }) end def short_description - i18n("short_description", tos_url: tos_url, base_path: Discourse.base_path) + i18n("short_description", vars: { tos_url: tos_url, base_path: Discourse.base_path }) end def name_key - PostActionType.types[object.id] + PostActionType.types[object.id].to_s end protected - def i18n(field, vars = nil) + def i18n(field, default: nil, vars: nil) key = "post_action_types.#{name_key}.#{field}" - vars ? I18n.t(key, vars) : I18n.t(key) + vars ? I18n.t(key, vars, default: default) : I18n.t(key, default: default) end end diff --git a/app/serializers/reviewable_score_type_serializer.rb b/app/serializers/reviewable_score_type_serializer.rb index 5db84b89201..5737134f9ab 100644 --- a/app/serializers/reviewable_score_type_serializer.rb +++ b/app/serializers/reviewable_score_type_serializer.rb @@ -9,8 +9,9 @@ class ReviewableScoreTypeSerializer < ApplicationSerializer # Allow us to share post action type translations for backwards compatibility def title - I18n.t("post_action_types.#{ReviewableScore.types[id]}.title", default: nil) || - I18n.t("reviewable_score_types.#{ReviewableScore.types[id]}.title") + I18n.t("post_action_types.#{type}.title", default: nil) || + I18n.t("reviewable_score_types.#{type}.title", default: nil) || + PostActionType.flag_settings.names[id] end def reviewable_priority diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index 1f5bed13b85..d420a116e65 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -350,13 +350,6 @@ class SiteSerializer < ApplicationSerializer private def ordered_flags(flags) - notify_moderators_type = PostActionType.flag_types[:notify_moderators] - types = flags - - if notify_moderators_flag = types.index(notify_moderators_type) - types.insert(types.length, types.delete_at(notify_moderators_flag)) - end - - types.map { |id| PostActionType.new(id: id) } + flags.map { |id| PostActionType.new(id: id) } end end diff --git a/app/serializers/topic_flag_type_serializer.rb b/app/serializers/topic_flag_type_serializer.rb index ccee54a0aaf..a53a09356ff 100644 --- a/app/serializers/topic_flag_type_serializer.rb +++ b/app/serializers/topic_flag_type_serializer.rb @@ -3,8 +3,8 @@ class TopicFlagTypeSerializer < PostActionTypeSerializer protected - def i18n(field, vars = nil) + def i18n(field, default: nil, vars: nil) key = "topic_flag_types.#{name_key}.#{field}" - vars ? I18n.t(key, vars) : I18n.t(key) + vars ? I18n.t(key, vars, default: default) : I18n.t(key, default: default) end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2711e449c50..db4461ba88c 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3771,6 +3771,7 @@ en: illegal: "You flagged this as illegal" notify_moderators: "You flagged this for moderation" notify_user: "You sent a message to this user" + custom: "You flagged this topic as %{custom}" delete: confirm: diff --git a/db/fixtures/003_flags.rb b/db/fixtures/003_flags.rb new file mode 100644 index 00000000000..2e5aa43d1a3 --- /dev/null +++ b/db/fixtures/003_flags.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +Flag.seed do |s| + s.id = 3 + s.name = "off_topic" + s.position = 2 + s.notify_type = true + s.auto_action_type = true + s.custom_type = false + s.applies_to = %w[Post Chat::Message] +end +Flag.seed do |s| + s.id = 4 + s.name = "inappropriate" + s.position = 3 + s.notify_type = true + s.auto_action_type = true + s.custom_type = false + s.applies_to = %w[Post Topic Chat::Message] +end +Flag.seed do |s| + s.id = 8 + s.name = "spam" + s.position = 4 + s.notify_type = true + s.auto_action_type = true + s.custom_type = false + s.applies_to = %w[Post Topic Chat::Message] +end +Flag.seed do |s| + s.id = 6 + s.name = "notify_user" + s.position = 0 + s.notify_type = false + s.auto_action_type = false + s.custom_type = true + s.applies_to = %w[Post Chat::Message] +end +Flag.seed do |s| + s.id = 7 + s.name = "notify_moderators" + s.position = 1 + s.notify_type = true + s.auto_action_type = false + s.custom_type = true + s.applies_to = %w[Post Topic Chat::Message] +end +Flag.seed do |s| + s.id = 10 + s.name = "illegal" + s.position = 5 + s.notify_type = true + s.auto_action_type = false + s.custom_type = true + s.applies_to = %w[Post Topic Chat::Message] +end diff --git a/db/migrate/20181031165343_add_flag_stats_to_user.rb b/db/migrate/20181031165343_add_flag_stats_to_user.rb index fa60e609557..6e0d42c00d5 100644 --- a/db/migrate/20181031165343_add_flag_stats_to_user.rb +++ b/db/migrate/20181031165343_add_flag_stats_to_user.rb @@ -18,7 +18,7 @@ class AddFlagStatsToUser < ActiveRecord::Migration[5.2] SUM(CASE WHEN pa.deferred_at IS NOT NULL THEN 1 ELSE 0 END) as flags_ignored FROM post_actions AS pa INNER JOIN users AS u ON u.id = pa.user_id - WHERE pa.post_action_type_id IN (#{PostActionType.notify_flag_types.values.join(", ")}) + WHERE pa.post_action_type_id IN (3, 4, 8, 7, 10) AND pa.user_id > 0 GROUP BY u.id ) AS x diff --git a/db/migrate/20240423054323_create_flags.rb b/db/migrate/20240423054323_create_flags.rb new file mode 100644 index 00000000000..655785336e6 --- /dev/null +++ b/db/migrate/20240423054323_create_flags.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateFlags < ActiveRecord::Migration[7.0] + def change + create_table :flags do |t| + t.string :name, unique: true + t.string :name_key, unique: true + t.text :description + t.boolean :notify_type, default: false, null: false + t.boolean :auto_action_type, default: false, null: false + t.boolean :custom_type, default: false, null: false + t.string :applies_to, array: true, null: false + t.integer :position, null: false + t.boolean :enabled, default: true, null: false + t.timestamps + end + # IDs below 1000 are reserved for system flags + DB.exec("SELECT setval('flags_id_seq', #{Flag::MAX_SYSTEM_FLAG_ID + 1}, FALSE);") + end +end diff --git a/lib/flag_settings.rb b/lib/flag_settings.rb index d88c01e35af..7dbce78331f 100644 --- a/lib/flag_settings.rb +++ b/lib/flag_settings.rb @@ -7,6 +7,7 @@ class FlagSettings :topic_flag_types, :auto_action_types, :custom_types, + :names, ) def initialize @@ -16,18 +17,28 @@ class FlagSettings @auto_action_types = Enum.new @custom_types = Enum.new @without_custom_types = Enum.new + @names = Enum.new end - def add(id, name, topic_type: nil, notify_type: nil, auto_action_type: nil, custom_type: nil) - @all_flag_types[name] = id - @topic_flag_types[name] = id if !!topic_type - @notify_types[name] = id if !!notify_type - @auto_action_types[name] = id if !!auto_action_type + def add( + id, + name_key, + topic_type: nil, + notify_type: nil, + auto_action_type: nil, + custom_type: nil, + name: nil + ) + @all_flag_types[name_key] = id + @topic_flag_types[name_key] = id if !!topic_type + @notify_types[name_key] = id if !!notify_type + @auto_action_types[name_key] = id if !!auto_action_type + @names[id] = name if name if !!custom_type - @custom_types[name] = id + @custom_types[name_key] = id else - @without_custom_types[name] = id + @without_custom_types[name_key] = id end end diff --git a/lib/guardian.rb b/lib/guardian.rb index 63846a7a742..b88c9292d55 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -4,6 +4,7 @@ require "guardian/bookmark_guardian" require "guardian/category_guardian" require "guardian/ensure_magic" require "guardian/group_guardian" +require "guardian/flag_guardian" require "guardian/post_guardian" require "guardian/post_revision_guardian" require "guardian/sidebar_guardian" @@ -16,6 +17,7 @@ class Guardian include BookmarkGuardian include CategoryGuardian include EnsureMagic + include FlagGuardian include GroupGuardian include PostGuardian include PostRevisionGuardian diff --git a/lib/guardian/flag_guardian.rb b/lib/guardian/flag_guardian.rb new file mode 100644 index 00000000000..302f84b9a74 --- /dev/null +++ b/lib/guardian/flag_guardian.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module FlagGuardian + def can_edit_flag?(flag) + @user.admin? && !flag.system? && !flag.used? + end +end diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index a29e5b13ab5..290c69d6e56 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -197,6 +197,9 @@ class Plugin::Instance # Applies to all sites in a multisite environment. Ignores plugin.enabled? def replace_flags(settings: ::FlagSettings.new, score_type_names: []) + Discourse.deprecate( + "replace flags should not be used as flags were moved to the database. Instead, a flag record should be added to the database. Alternatively, soon, the admin will be able to do this in the admin panel.", + ) next_flag_id = ReviewableScore.types.values.max + 1 yield(settings, next_flag_id) if block_given? diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js b/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js index bb2f1b7e591..febcfceddad 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js @@ -35,7 +35,10 @@ export default class ChatMessageFlag { return flags.map((flag) => { flag.set( "description", - I18n.t(`chat.flags.${flag.name_key}`, { basePath: getURL("") }) + I18n.t(`chat.flags.${flag.name_key}`, { + basePath: getURL(""), + defaultValue: "", + }) ); return flag; }); diff --git a/plugins/chat/spec/services/chat/flag_message_spec.rb b/plugins/chat/spec/services/chat/flag_message_spec.rb index f3e326604ce..499c2183053 100644 --- a/plugins/chat/spec/services/chat/flag_message_spec.rb +++ b/plugins/chat/spec/services/chat/flag_message_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Chat::FlagMessage do let(:guardian) { Guardian.new(current_user) } let(:channel_id) { channel_1.id } let(:message_id) { message_1.id } - let(:flag_type_id) { ReviewableScore.types.values.first } + let(:flag_type_id) { ReviewableScore.types[:off_topic] } let(:message) { nil } let(:is_warning) { nil } let(:take_action) { nil } diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb index e38c195de2e..6a05d12755e 100644 --- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb +++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb @@ -1032,7 +1032,7 @@ RSpec.describe DiscourseNarrativeBot::NewUserNarrative do let(:another_post) { Fabricate(:post, user: discobot_user, topic: topic) } let(:flag) do Fabricate( - :flag, + :flag_post_action, post: post, user: user, post_action_type_id: PostActionType.types[:inappropriate], @@ -1040,7 +1040,7 @@ RSpec.describe DiscourseNarrativeBot::NewUserNarrative do end let(:other_flag) do Fabricate( - :flag, + :flag_post_action, post: another_post, user: user, post_action_type_id: PostActionType.types[:spam], @@ -1166,7 +1166,7 @@ RSpec.describe DiscourseNarrativeBot::NewUserNarrative do describe "when post contain the right answer" do let(:post) { Fabricate(:post, user: discobot_user, topic: topic) } - let(:flag) { Fabricate(:flag, post: post, user: user) } + let(:flag) { Fabricate(:flag_post_action, post: post, user: user) } before do narrative.set_data(user, state: :tutorial_flag, topic_id: topic.id) diff --git a/spec/fabricators/flag_fabricator.rb b/spec/fabricators/flag_fabricator.rb index d4b30c52377..792da20f55f 100644 --- a/spec/fabricators/flag_fabricator.rb +++ b/spec/fabricators/flag_fabricator.rb @@ -1,7 +1,3 @@ # frozen_string_literal: true -Fabricator(:flag, from: :post_action) do - user - post - post_action_type_id PostActionType.types[:spam] -end +Fabricator(:flag) { name "offtopic", applies_to { %w[Post Chat::Message] } } diff --git a/spec/fabricators/flag_post_action_fabricator.rb b/spec/fabricators/flag_post_action_fabricator.rb new file mode 100644 index 00000000000..77f44a6078f --- /dev/null +++ b/spec/fabricators/flag_post_action_fabricator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +Fabricator(:flag_post_action, from: :post_action) do + user + post + post_action_type_id PostActionType.types[:spam] +end diff --git a/spec/lib/composer_messages_finder_spec.rb b/spec/lib/composer_messages_finder_spec.rb index 62b3776fd42..1d9c09d7fb9 100644 --- a/spec/lib/composer_messages_finder_spec.rb +++ b/spec/lib/composer_messages_finder_spec.rb @@ -360,7 +360,7 @@ RSpec.describe ComposerMessagesFinder do end it "shows a message when the replier has already flagged the post" do - Fabricate(:flag, post: self_flagged_post, user: user) + Fabricate(:flag_post_action, post: self_flagged_post, user: user) finder = ComposerMessagesFinder.new( user, @@ -372,13 +372,13 @@ RSpec.describe ComposerMessagesFinder do end it "shows a message when replying to flagged topic (first post)" do - Fabricate(:flag, post: original_post, user: user) + Fabricate(:flag_post_action, post: original_post, user: user) finder = ComposerMessagesFinder.new(user, composer_action: "reply", topic_id: topic.id) expect(finder.check_dont_feed_the_trolls).to be_present end it "does not show a message when not enough others have flagged the post" do - Fabricate(:flag, post: under_flagged_post, user: other_user) + Fabricate(:flag_post_action, post: under_flagged_post, user: other_user) finder = ComposerMessagesFinder.new( user, @@ -392,7 +392,12 @@ RSpec.describe ComposerMessagesFinder do it "does not show a message when the flag has already been resolved" do SiteSetting.dont_feed_the_trolls_threshold = 1 - Fabricate(:flag, post: resolved_flag_post, user: other_user, disagreed_at: 1.hour.ago) + Fabricate( + :flag_post_action, + post: resolved_flag_post, + user: other_user, + disagreed_at: 1.hour.ago, + ) finder = ComposerMessagesFinder.new( user, @@ -404,8 +409,8 @@ RSpec.describe ComposerMessagesFinder do end it "shows a message when enough others have already flagged the post" do - Fabricate(:flag, post: over_flagged_post, user: other_user) - Fabricate(:flag, post: over_flagged_post, user: third_user) + Fabricate(:flag_post_action, post: over_flagged_post, user: other_user) + Fabricate(:flag_post_action, post: over_flagged_post, user: third_user) finder = ComposerMessagesFinder.new( user, diff --git a/spec/lib/guardian/flag_guardian_spec.rb b/spec/lib/guardian/flag_guardian_spec.rb new file mode 100644 index 00000000000..1ae97406d29 --- /dev/null +++ b/spec/lib/guardian/flag_guardian_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.describe FlagGuardian do + fab!(:user) + fab!(:admin) + fab!(:flag) + + describe "#can_edit_flag?" do + it "returns true for admin and false for regular user" do + expect(Guardian.new(admin).can_edit_flag?(flag)).to eq(true) + expect(Guardian.new(user).can_edit_flag?(flag)).to eq(false) + end + + it "returns false when flag is system" do + expect(Guardian.new(admin).can_edit_flag?(Flag.system.first)).to eq(false) + end + + it "returns false when flag was already used with post action" do + Fabricate(:post_action, post_action_type_id: flag.id) + expect(Guardian.new(admin).can_edit_flag?(flag)).to eq(false) + end + + it "returns false when flag was already used with reviewable" do + Fabricate(:reviewable_score, reviewable_score_type: flag.id) + expect(Guardian.new(admin).can_edit_flag?(flag)).to eq(false) + end + end +end diff --git a/spec/lib/plugin/instance_spec.rb b/spec/lib/plugin/instance_spec.rb index bd72cfc57b0..4488bb45207 100644 --- a/spec/lib/plugin/instance_spec.rb +++ b/spec/lib/plugin/instance_spec.rb @@ -673,10 +673,7 @@ TEXT end describe "#replace_flags" do - after do - PostActionType.replace_flag_settings(nil) - ReviewableScore.reload_types - end + after { PostActionType.replace_flag_settings(nil) } let(:original_flags) { PostActionType.flag_settings } diff --git a/spec/lib/post_revisor_spec.rb b/spec/lib/post_revisor_spec.rb index ffff3bcfdc7..7c669e52a66 100644 --- a/spec/lib/post_revisor_spec.rb +++ b/spec/lib/post_revisor_spec.rb @@ -484,7 +484,7 @@ RSpec.describe PostRevisor do post = Fabricate(:post, raw: "hello world") - Fabricate(:flag, post: post, user: user) + Fabricate(:flag_post_action, post: post, user: user) revisor = PostRevisor.new(post) revisor.revise!( diff --git a/spec/models/flag_spec.rb b/spec/models/flag_spec.rb new file mode 100644 index 00000000000..9f384d947e8 --- /dev/null +++ b/spec/models/flag_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +RSpec.describe Flag, type: :model do + before { Flag.reset_flag_settings! } + + it "has id lower than 1000 for system flags" do + flag = Fabricate(:flag, id: 1) + expect(flag.system?).to be true + end + + it "has id greater than 1000 for non-system flags" do + flag = Fabricate(:flag) + expect(flag.system?).to be false + expect(flag.id).to be > 1000 + end + + it "has correct name key" do + flag = Fabricate(:flag, name: "CuStOm Flag!!!") + expect(flag.name_key).to eq("custom_flag") + + flag.update!(name: "It's Illegal") + expect(flag.name_key).to eq("its_illegal") + + flag.update!(name: "THIS IS SPaM!+)(*&^%$#@@@!)") + expect(flag.name_key).to eq("this_is_spam") + end + + it "updates post action types when created, modified or destroyed" do + expect(PostActionType.flag_types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal], + ) + expect(ReviewableScore.types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal needs_approval], + ) + + flag = Fabricate(:flag, name: "custom") + expect(PostActionType.flag_types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal custom], + ) + expect(ReviewableScore.types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal custom needs_approval], + ) + + flag.update!(name: "edited_custom") + expect(PostActionType.flag_types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal edited_custom], + ) + expect(ReviewableScore.types.keys).to eq( + %i[ + notify_user + notify_moderators + off_topic + inappropriate + spam + illegal + edited_custom + needs_approval + ], + ) + + flag.destroy! + expect(PostActionType.flag_types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal], + ) + expect(ReviewableScore.types.keys).to eq( + %i[notify_user notify_moderators off_topic inappropriate spam illegal needs_approval], + ) + end +end diff --git a/spec/models/trust_level3_requirements_spec.rb b/spec/models/trust_level3_requirements_spec.rb index 5ae1c9d2089..0f96e42368f 100644 --- a/spec/models/trust_level3_requirements_spec.rb +++ b/spec/models/trust_level3_requirements_spec.rb @@ -359,7 +359,7 @@ RSpec.describe TrustLevel3Requirements do flags = %i[off_topic inappropriate notify_user notify_moderators spam].map do |t| Fabricate( - :flag, + :flag_post_action, post: Fabricate(:post, user: user), post_action_type_id: PostActionType.types[t], agreed_at: 1.minute.ago, @@ -369,7 +369,7 @@ RSpec.describe TrustLevel3Requirements do _deferred_flags = %i[off_topic inappropriate notify_user notify_moderators spam].map do |t| Fabricate( - :flag, + :flag_post_action, post: Fabricate(:post, user: user), post_action_type_id: PostActionType.types[t], deferred_at: 1.minute.ago, @@ -379,7 +379,7 @@ RSpec.describe TrustLevel3Requirements do _deleted_flags = %i[off_topic inappropriate notify_user notify_moderators spam].map do |t| Fabricate( - :flag, + :flag_post_action, post: Fabricate(:post, user: user), post_action_type_id: PostActionType.types[t], deleted_at: 1.minute.ago, @@ -388,7 +388,7 @@ RSpec.describe TrustLevel3Requirements do # Same post, different user: Fabricate( - :flag, + :flag_post_action, post: flags[1].post, post_action_type_id: PostActionType.types[:spam], agreed_at: 1.minute.ago, @@ -396,7 +396,7 @@ RSpec.describe TrustLevel3Requirements do # Flagged their own post: Fabricate( - :flag, + :flag_post_action, user: user, post: Fabricate(:post, user: user), post_action_type_id: PostActionType.types[:spam], @@ -405,7 +405,7 @@ RSpec.describe TrustLevel3Requirements do # More than 100 days ago: Fabricate( - :flag, + :flag_post_action, post: Fabricate(:post, user: user, created_at: 101.days.ago), post_action_type_id: PostActionType.types[:spam], created_at: 101.days.ago, diff --git a/spec/services/auto_silence_spec.rb b/spec/services/auto_silence_spec.rb index b29aaa18442..bf029934784 100644 --- a/spec/services/auto_silence_spec.rb +++ b/spec/services/auto_silence_spec.rb @@ -72,7 +72,11 @@ RSpec.describe SpamRule::AutoSilence do end it "returns 0 when there is one flag that has a reason other than spam" do - Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:off_topic]) + Fabricate( + :flag_post_action, + post: post, + post_action_type_id: PostActionType.types[:off_topic], + ) expect(count).to eq(0) end diff --git a/spec/system/composer/dont_feed_the_trolls_popup_spec.rb b/spec/system/composer/dont_feed_the_trolls_popup_spec.rb index 4edec527dc8..70f37965442 100644 --- a/spec/system/composer/dont_feed_the_trolls_popup_spec.rb +++ b/spec/system/composer/dont_feed_the_trolls_popup_spec.rb @@ -6,7 +6,7 @@ describe "Composer don't feed the trolls popup", type: :system do fab!(:topic) { Fabricate(:topic, user: user) } fab!(:post) { Fabricate(:post, user: user, topic: topic) } fab!(:reply) { Fabricate(:post, user: troll, topic: topic) } - fab!(:flag) { Fabricate(:flag, post: reply, user: user) } + fab!(:flag) { Fabricate(:flag_post_action, post: reply, user: user) } let(:topic_page) { PageObjects::Pages::Topic.new } before { sign_in user } diff --git a/spec/system/flagging_post_spec.rb b/spec/system/flagging_post_spec.rb index 50cb7ebbb91..ea6d2c9f29f 100644 --- a/spec/system/flagging_post_spec.rb +++ b/spec/system/flagging_post_spec.rb @@ -12,7 +12,7 @@ describe "Flagging post", type: :system do describe "Using Take Action" do it "can select the default action to hide the post, agree with other flags, and reach the flag threshold" do - other_flag = Fabricate(:flag, post: post_to_flag, user: Fabricate(:moderator)) + other_flag = Fabricate(:flag_post_action, post: post_to_flag, user: Fabricate(:moderator)) other_flag_reviewable = Fabricate(:reviewable_flagged_post, target: post_to_flag, created_by: other_flag.user) expect(other_flag.reload.agreed_at).to be_nil