diff --git a/app/models/search/tag_indexer.rb b/app/models/search/tag_indexer.rb index 0218a7dfa26..e0549cd2759 100644 --- a/app/models/search/tag_indexer.rb +++ b/app/models/search/tag_indexer.rb @@ -23,6 +23,8 @@ def self.mapping }, tag_type: { type: "keyword" }, sortable_name: { type: "keyword" }, + canonized_at: { type: "date" }, + decanonized_at: { type: "date" }, uses: { type: "integer" }, unwrangled: { type: "boolean" } } @@ -72,7 +74,7 @@ def document(object) root: false, only: [ :id, :name, :sortable_name, :merger_id, :canonical, :created_at, - :unwrangleable + :canonized_at, :decanonized_at, :unwrangleable ] ).merge( has_posted_works: object.has_posted_works?, diff --git a/app/models/search/tag_query.rb b/app/models/search/tag_query.rb index bcbb906ddb8..dd3e5f20a50 100644 --- a/app/models/search/tag_query.rb +++ b/app/models/search/tag_query.rb @@ -52,15 +52,19 @@ def sort when "created_at" column = "created_at" direction ||= "desc" + when "canonized_at" + column = "canonized_at" + direction ||= "desc" + when "decanonized_at" + column = "decanonized_at" + direction ||= "desc" else column = "name.keyword" direction ||= "asc" end sort_hash = { column => { order: direction } } - if column == "created_at" - sort_hash[column][:unmapped_type] = "date" - end + sort_hash[column][:unmapped_type] = "date" if %w[created_at canonized_at decanonized_at].include?(column) sort_by_id = { id: { order: direction } } diff --git a/app/models/search/tag_search_form.rb b/app/models/search/tag_search_form.rb index e2312b7dbbe..243c0b7aff8 100644 --- a/app/models/search/tag_search_form.rb +++ b/app/models/search/tag_search_form.rb @@ -68,11 +68,13 @@ def sort_options [ %w[Name name], ["Date Created", "created_at"], + ["Date Canonized", "canonized_at"], + ["Date Decanonized", "decanonized_at"], %w[Uses uses] ] end def default_sort_direction - %w[created_at uses].include?(sort_column) ? "desc" : "asc" + %w[created_at canonized_at decanonized_at uses].include?(sort_column) ? "desc" : "asc" end end diff --git a/app/models/tag.rb b/app/models/tag.rb index 95c7a4aa0e9..ed2b785414e 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -255,6 +255,15 @@ def update_wrangler(tag) end end + before_save :set_canonization_date, if: :will_save_change_to_canonical? + def set_canonization_date + if canonical? + self.canonized_at = Time.current + else + self.decanonized_at = Time.current + end + end + after_save :check_type_changes, if: :saved_change_to_type? def check_type_changes return if type_before_last_save.nil? diff --git a/app/views/tags/edit.html.erb b/app/views/tags/edit.html.erb index a66c46320ba..518f9642b4e 100644 --- a/app/views/tags/edit.html.erb +++ b/app/views/tags/edit.html.erb @@ -13,6 +13,16 @@ <% if logged_in_as_admin? %>

<%= ts("Last updated by %{wrangler} on %{date}", wrangler: @tag.last_wrangler.try(:login) || '---', date: @tag.updated_at) %>

+ <% if @tag.canonized_at || @tag.decanonized_at %> +

+ <% if @tag.canonized_at %> + <%= t(".last_canonized", date: @tag.canonized_at.utc.strftime("%Y-%m-%d %H:%M:%S")) %> + <% end %> + <% if @tag.decanonized_at %> + <%= t(".last_decanonized", date: @tag.decanonized_at.utc.strftime("%Y-%m-%d %H:%M:%S")) %> + <% end %> +

+ <% end %> <% end %> <%= form_for @tag, as: :tag, url: { action: "update", id: @tag}, html: { method: :put } do |f| %> diff --git a/config/locales/views/en.yml b/config/locales/views/en.yml index 03b5b0cf4cb..cdfbe7b0ca1 100644 --- a/config/locales/views/en.yml +++ b/config/locales/views/en.yml @@ -3078,6 +3078,8 @@ en: wrangling_tools: Wrangling Tools tags: edit: + last_canonized: Last canonized on %{date} UTC. + last_decanonized: Last decanonized on %{date} UTC. save_changes: Save changes submit_legend: Submit index: diff --git a/db/migrate/20260725153049_add_canonization_dates_to_tags.rb b/db/migrate/20260725153049_add_canonization_dates_to_tags.rb new file mode 100644 index 00000000000..7b6c56ed436 --- /dev/null +++ b/db/migrate/20260725153049_add_canonization_dates_to_tags.rb @@ -0,0 +1,10 @@ +class AddCanonizationDatesToTags < ActiveRecord::Migration[8.1] + uses_departure! if Rails.env.staging? || Rails.env.production? + + def change + change_table :tags, bulk: true do |t| + t.datetime :canonized_at, default: nil, null: true + t.datetime :decanonized_at, default: nil, null: true + end + end +end diff --git a/features/tags_and_wrangling/tag_wrangling_admin.feature b/features/tags_and_wrangling/tag_wrangling_admin.feature index 71bdadb3711..ca875d7a5d6 100644 --- a/features/tags_and_wrangling/tag_wrangling_admin.feature +++ b/features/tags_and_wrangling/tag_wrangling_admin.feature @@ -32,6 +32,36 @@ Feature: Tag wrangling Then I should not see "Amelie" And I should see "Amélie" + Scenario: Admin can see when a tag was last canonized and decanonized + + Given I am logged in as a "tag_wrangling" admin + And a fandom exists with name: "Amelie", canonical: false + When I edit the tag "Amelie" + Then I should not see "Last canonized on" + And I should not see "Last decanonized on" + When I check "Canonical" + And I press "Save changes" + And I edit the tag "Amelie" + Then I should see "Last canonized on" + And I should not see "Last decanonized on" + When I uncheck "Canonical" + And I press "Save changes" + And I edit the tag "Amelie" + Then I should see "Last canonized on" + And I should see "Last decanonized on" + + Scenario: Tag wrangler cannot see when a tag was last canonized or decanonized + + Given I am logged in as a "tag_wrangling" admin + And a fandom exists with name: "Amelie", canonical: true + When I edit the tag "Amelie" + And I uncheck "Canonical" + And I press "Save changes" + And I am logged in as a tag wrangler + And I edit the tag "Amelie" + Then I should not see "Last canonized on" + And I should not see "Last decanonized on" + Scenario: Admin can rename a tag using Eastern characters Given I am logged in as a "tag_wrangling" admin diff --git a/spec/models/search/tag_query_spec.rb b/spec/models/search/tag_query_spec.rb index 09cbd6e49c0..53b12e55f7e 100644 --- a/spec/models/search/tag_query_spec.rb +++ b/spec/models/search/tag_query_spec.rb @@ -197,6 +197,16 @@ expect(q.generated_query[:sort]).to eq([{ "created_at" => { order: "asc", unmapped_type: "date" } }, { id: { order: "asc" } }]) end + it "allows you to sort by Date Canonized" do + q = TagQuery.new(sort_column: "canonized_at") + expect(q.generated_query[:sort]).to eq([{ "canonized_at" => { order: "desc", unmapped_type: "date" } }, { id: { order: "desc" } }]) + end + + it "allows you to sort by Date Decanonized" do + q = TagQuery.new(sort_column: "decanonized_at") + expect(q.generated_query[:sort]).to eq([{ "decanonized_at" => { order: "desc", unmapped_type: "date" } }, { id: { order: "desc" } }]) + end + it "allows you to sort by Uses" do q = TagQuery.new(sort_column: "uses") expect(q.generated_query[:sort]).to eq([{ "uses" => { order: "desc" } }, { "name.keyword" => { order: "asc" } }, { id: { order: "desc" } }]) diff --git a/spec/models/tag_wrangling_spec.rb b/spec/models/tag_wrangling_spec.rb index f5a56578546..2209424ca2a 100644 --- a/spec/models/tag_wrangling_spec.rb +++ b/spec/models/tag_wrangling_spec.rb @@ -18,6 +18,42 @@ end.to(add_to_reindex_queue(work, :background) & not_add_to_reindex_queue(work, :main)) end + + it "sets canonized_at" do + freeze_time do + fandom.update!(canonical: true) + expect(fandom.canonized_at).to eq(Time.current) + expect(fandom.decanonized_at).to be_nil + end + end + end + + context "when a tag is created as canonical" do + it "sets canonized_at" do + freeze_time do + fandom = create(:canonical_fandom) + expect(fandom.canonized_at).to eq(Time.current) + expect(fandom.decanonized_at).to be_nil + end + end + end + + context "when a tag is created as non-canonical" do + it "does not set canonized_at or decanonized_at" do + fandom = create(:fandom) + expect(fandom.canonized_at).to be_nil + expect(fandom.decanonized_at).to be_nil + end + end + + context "when canonical does not change" do + it "does not change canonized_at or decanonized_at" do + fandom = create(:canonical_fandom) + expect do + fandom.update!(unwrangleable: false) + end.to avoid_changing { fandom.reload.canonized_at } & + avoid_changing { fandom.reload.decanonized_at } + end end context "when canonical becomes false" do @@ -37,6 +73,13 @@ not_add_to_reindex_queue(work, :main)) end + it "sets decanonized_at" do + freeze_time do + fandom.update!(canonical: false) + expect(fandom.decanonized_at).to eq(Time.current) + end + end + it "removes favorite tags" do user = create(:user) user.favorite_tags.create(tag: fandom)