Several fixes for the "This tag is under discussion" notice on the post index page: * Fix the notice appearing for BURs that aren't pending. * Fix the notice never going away because of the cache never expiring. * List all topics when a tag is involved in multiple BURs. * Link to the forum post instead of the forum topic (fix #4421). * Optimization: don't check for BURs when the search isn't a simple single tag search. * Add a `tags` field to the bulk update requests table for tracking all tags involved in the request (excluding tags in mass updates that are negated/optional/wildcards). Known issue: doesn't handle tag type prefixes in mass updates correctly (e.g. `mass update foo -> artist:bar` doesn't detect the tag `bar`). * Allow searching the /bulk_update_requests page by tags. We don't really need to cache the notice here, but we do it anyway to reduce queries on the post index page.
180 lines
5.2 KiB
Ruby
180 lines
5.2 KiB
Ruby
class BulkUpdateRequest < ApplicationRecord
|
|
attr_accessor :title
|
|
attr_accessor :reason
|
|
attr_reader :skip_secondary_validations
|
|
|
|
belongs_to :user
|
|
belongs_to :forum_topic, optional: true
|
|
belongs_to :forum_post, optional: true
|
|
belongs_to :approver, optional: true, class_name: "User"
|
|
|
|
before_validation :normalize_text
|
|
before_validation :update_tags
|
|
validates_presence_of :script
|
|
validates_presence_of :title, if: ->(rec) {rec.forum_topic_id.blank?}
|
|
validates_presence_of :forum_topic, if: ->(rec) {rec.forum_topic_id.present?}
|
|
validates_inclusion_of :status, :in => %w(pending approved rejected)
|
|
validate :script_formatted_correctly
|
|
validate :validate_script, :on => :create
|
|
|
|
after_create :create_forum_topic
|
|
|
|
scope :pending_first, -> { order(Arel.sql("(case status when 'pending' then 0 when 'approved' then 1 else 2 end)")) }
|
|
scope :pending, -> {where(status: "pending")}
|
|
scope :approved, -> { where(status: "approved") }
|
|
scope :rejected, -> { where(status: "rejected") }
|
|
scope :has_topic, -> { where.not(forum_topic: nil) }
|
|
scope :expired, -> {where("created_at < ?", TagRelationship::EXPIRY.days.ago)}
|
|
scope :old, -> {where("created_at between ? and ?", TagRelationship::EXPIRY.days.ago, TagRelationship::EXPIRY_WARNING.days.ago)}
|
|
|
|
module SearchMethods
|
|
def default_order
|
|
pending_first.order(id: :desc)
|
|
end
|
|
|
|
def search(params = {})
|
|
q = super
|
|
|
|
q = q.search_attributes(params, :user, :approver, :forum_topic_id, :forum_post_id, :script, :tags)
|
|
q = q.text_attribute_matches(:script, params[:script_matches])
|
|
|
|
if params[:status].present?
|
|
q = q.where(status: params[:status].split(","))
|
|
end
|
|
|
|
params[:order] ||= "status_desc"
|
|
case params[:order]
|
|
when "id_desc"
|
|
q = q.order(id: :desc)
|
|
when "id_asc"
|
|
q = q.order(id: :asc)
|
|
when "updated_at_desc"
|
|
q = q.order(updated_at: :desc)
|
|
when "updated_at_asc"
|
|
q = q.order(updated_at: :asc)
|
|
else
|
|
q = q.apply_default_order(params)
|
|
end
|
|
|
|
q
|
|
end
|
|
end
|
|
|
|
module ApprovalMethods
|
|
def forum_updater
|
|
@forum_updater ||= begin
|
|
post = if forum_topic
|
|
forum_post || forum_topic.forum_posts.first
|
|
else
|
|
nil
|
|
end
|
|
ForumUpdater.new(forum_topic, forum_post: post)
|
|
end
|
|
end
|
|
|
|
def approve!(approver)
|
|
transaction do
|
|
CurrentUser.scoped(approver) do
|
|
AliasAndImplicationImporter.new(script, forum_topic_id, "1", true).process!
|
|
update!(status: "approved", approver: approver, skip_secondary_validations: true)
|
|
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has been approved by @#{approver.name}.")
|
|
end
|
|
end
|
|
rescue AliasAndImplicationImporter::Error => x
|
|
self.approver = approver
|
|
CurrentUser.scoped(approver) do
|
|
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has failed: #{x}")
|
|
end
|
|
end
|
|
|
|
def create_forum_topic
|
|
CurrentUser.as(user) do
|
|
self.forum_topic = ForumTopic.create(title: title, category_id: 1, creator: user) unless forum_topic.present?
|
|
self.forum_post = forum_topic.forum_posts.create(body: reason_with_link, creator: user) unless forum_post.present?
|
|
save
|
|
end
|
|
end
|
|
|
|
def reject!(rejector = User.system)
|
|
transaction do
|
|
update!(status: "rejected")
|
|
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has been rejected by @#{rejector.name}.")
|
|
end
|
|
end
|
|
|
|
def bulk_update_request_link
|
|
%{"bulk update request ##{id}":/bulk_update_requests?search%5Bid%5D=#{id}}
|
|
end
|
|
end
|
|
|
|
module ValidationMethods
|
|
def script_formatted_correctly
|
|
AliasAndImplicationImporter.tokenize(script)
|
|
rescue StandardError => e
|
|
errors[:base] << e.message
|
|
end
|
|
|
|
def validate_script
|
|
AliasAndImplicationImporter.new(script, forum_topic_id, "1", skip_secondary_validations).validate!
|
|
rescue RuntimeError => e
|
|
errors[:base] << e.message
|
|
end
|
|
end
|
|
|
|
extend SearchMethods
|
|
include ApprovalMethods
|
|
include ValidationMethods
|
|
|
|
def reason_with_link
|
|
"[bur:#{id}]\n\nReason: #{reason}"
|
|
end
|
|
|
|
def script_with_links
|
|
tokens = AliasAndImplicationImporter.tokenize(script)
|
|
lines = tokens.map do |token|
|
|
case token[0]
|
|
when :create_alias, :create_implication, :remove_alias, :remove_implication
|
|
"#{token[0].to_s.tr("_", " ")} [[#{token[1]}]] -> [[#{token[2]}]]"
|
|
|
|
when :mass_update
|
|
"mass update {{#{token[1]}}} -> #{token[2]}"
|
|
|
|
when :change_category
|
|
"category [[#{token[1]}]] -> #{token[2]}"
|
|
|
|
else
|
|
raise "Unknown token: #{token[0]}"
|
|
end
|
|
end
|
|
lines.join("\n")
|
|
end
|
|
|
|
def normalize_text
|
|
self.script = script.downcase
|
|
end
|
|
|
|
def update_tags
|
|
self.tags = AliasAndImplicationImporter.new(script, nil).affected_tags
|
|
end
|
|
|
|
def skip_secondary_validations=(v)
|
|
@skip_secondary_validations = v.to_s.truthy?
|
|
end
|
|
|
|
def is_pending?
|
|
status == "pending"
|
|
end
|
|
|
|
def is_approved?
|
|
status == "approved"
|
|
end
|
|
|
|
def is_rejected?
|
|
status == "rejected"
|
|
end
|
|
|
|
def self.available_includes
|
|
[:user, :forum_topic, :forum_post, :approver]
|
|
end
|
|
end
|