Expire unused aliases and implications after 2 years of inactivity

This commit is contained in:
Albert Yi
2018-07-27 17:56:28 -07:00
parent cdb0a2cac7
commit ea4e7f1970
11 changed files with 167 additions and 170 deletions

View File

@@ -0,0 +1,67 @@
module TagRelationshipRetirementService
THRESHOLD = 2.year
extend self
def forum_topic_title
return "Retired tag aliases & implications"
end
def forum_topic_body
return "This topic deals with tag relationships created two or more years ago that have not been used since. They will be retired. This topic will be updated as an automated system retires expired relationships."
end
def dry_run
[TagAlias, TagImplication].each do |model|
each_candidate(model) do |rel|
puts "#{rel.relationship} #{rel.antecedent_name} -> #{rel.consequent_name} retired"
end
end
end
def forum_topic
topic = ForumTopic.where(title: forum_topic_title).first
if topic.nil?
topic = CurrentUser.as_system do
ForumTopic.create(title: forum_topic_title, category_id: 1, original_post_attributes: {body: forum_topic_body})
end
end
return topic
end
def find_and_retire!
messages = []
[TagAlias, TagImplication].each do |model|
each_candidate(model) do |rel|
rel.update(status: "retired")
messages << rel.retirement_message
end
end
updater = ForumUpdater.new(forum_topic)
updater.update(messages.sort.join("\n"))
end
def each_candidate(model)
model.active.where("created_at < ?", THRESHOLD.ago).find_each do |rel|
if is_unused?(rel.consequent_name)
yield(rel)
end
end
# model.active.where("created_at < ?", SMALL_THRESHOLD.ago).find_each do |rel|
# if is_underused?(rel.consequent_name)
# yield(rel)
# end
# end
end
def is_underused?(name)
(Tag.find_by_name(name).try(:post_count) || 0) < COUNT_THRESHOLD
end
def is_unused?(name)
return !Post.tag_match("status:any #{name}").where("created_at > ?", THRESHOLD.ago).exists?
end
end

View File

@@ -2,6 +2,7 @@ class TagAlias < TagRelationship
before_save :ensure_tags_exist
after_save :clear_all_cache
after_destroy :clear_all_cache
after_save :clear_all_cache, if: ->(rec) {rec.is_retired?}
after_save :create_mod_action
validates_uniqueness_of :antecedent_name
validate :absence_of_transitive_relation

View File

@@ -2,6 +2,7 @@ class TagImplication < TagRelationship
before_save :update_descendant_names
after_save :update_descendant_names_for_parents
after_destroy :update_descendant_names_for_parents
after_save :update_descendant_names_for_parents, if: ->(rec) { rec.is_retired? }
after_save :create_mod_action
validates_uniqueness_of :antecedent_name, :scope => :consequent_name
validate :absence_of_circular_relation

View File

@@ -13,13 +13,15 @@ class TagRelationship < ApplicationRecord
has_one :antecedent_tag, :class_name => "Tag", :foreign_key => "name", :primary_key => "antecedent_name"
has_one :consequent_tag, :class_name => "Tag", :foreign_key => "name", :primary_key => "consequent_name"
scope :active, ->{where(status: "active")}
scope :expired, ->{where("created_at < ?", EXPIRY.days.ago)}
scope :old, ->{where("created_at >= ? and created_at < ?", EXPIRY.days.ago, EXPIRY_WARNING.days.ago)}
scope :pending, ->{where(status: "pending")}
scope :retired, ->{where(status: "retired")}
before_validation :initialize_creator, :on => :create
before_validation :normalize_names
validates_format_of :status, :with => /\A(active|deleted|pending|processing|queued|error: .*)\Z/
validates_format_of :status, :with => /\A(active|deleted|pending|processing|queued|retired|error: .*)\Z/
validates_presence_of :creator_id, :antecedent_name, :consequent_name
validates :creator, presence: { message: "must exist" }, if: -> { creator_id.present? }
validates :approver, presence: { message: "must exist" }, if: -> { approver_id.present? }
@@ -35,6 +37,10 @@ class TagRelationship < ApplicationRecord
self.consequent_name = consequent_name.mb_chars.downcase.tr(" ", "_")
end
def is_retired?
status == "retired"
end
def is_pending?
status == "pending"
end
@@ -98,6 +104,8 @@ class TagRelationship < ApplicationRecord
if params[:status].present?
q = q.status_matches(params[:status])
else
q = q.active
end
if params[:category].present?
@@ -140,6 +148,10 @@ class TagRelationship < ApplicationRecord
"The #{relationship} [[#{antecedent_name}]] -> [[#{consequent_name}]] #{forum_link} has been rejected by @#{rejector.name}."
end
def retirement_message
"The #{relationship} [[#{antecedent_name}]] -> [[#{consequent_name}]] #{forum_link} has been retired."
end
def conflict_message
"The tag alias [[#{antecedent_name}]] -> [[#{consequent_name}]] #{forum_link} has conflicting wiki pages. [[#{consequent_name}]] should be updated to include information from [[#{antecedent_name}]] if necessary."
end

View File

@@ -2,7 +2,7 @@
<div id="a-index">
<%= simple_form_for(:search, method: :get, url: tag_aliases_path, defaults: { required: false }, html: { class: "inline-form" }) do |f| %>
<%= f.input :name_matches, label: "Name", input_html: { value: params[:search][:name_matches], data: { autocomplete: "tag" } } %>
<%= f.input :status, label: "Status", collection: ["", "Approved", "Pending"], selected: params[:search][:status] %>
<%= f.input :status, label: "Status", collection: ["", "Approved", "Pending", "Retired"], selected: params[:search][:status] %>
<%= f.input :category, label: "Category", collection: TagCategory.canonical_mapping.to_a, include_blank: true, selected: params[:search][:category] %>
<%= f.input :order, label: "Order", collection: [%w[Status status], %w[Recently\ created created_at], %w[Recently\ updated updated_at], %w[Name name], %w[Tag\ count tag_count]], selected: params[:search][:order] %>
<%= f.submit "Search" %>

View File

@@ -2,7 +2,7 @@
<div id="a-index">
<%= simple_form_for(:search, method: :get, url: tag_implications_path, defaults: { required: false }, html: { class: "inline-form" }) do |f| %>
<%= f.input :name_matches, label: "Name", input_html: { value: params[:search][:name_matches], data: { autocomplete: "tag" } } %>
<%= f.input :status, label: "Status", collection: ["", "Approved", "Pending"], selected: params[:search][:status] %>
<%= f.input :status, label: "Status", collection: ["", "Approved", "Pending", "Retired"], selected: params[:search][:status] %>
<%= f.input :category, label: "Category", collection: TagCategory.canonical_mapping.to_a, include_blank: true, selected: params[:search][:category] %>
<%= f.input :order, label: "Order", collection: [%w[Status status], %w[Recently\ created created_at], %w[Recently\ updated updated_at], %w[Name name], %w[Tag\ count tag_count]], selected: params[:search][:order] %>
<%= f.submit "Search" %>