diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb index f2c28328f86..f72a0325ab7 100644 --- a/app/controllers/admin/tags_controller.rb +++ b/app/controllers/admin/tags_controller.rb @@ -17,12 +17,15 @@ module Admin authorize @tag, :show? @time_period = report_range + @action_logs = Admin::ActionLogFilter.new(target_tag: @tag.formatted_name).results.limit(5) end def update authorize @tag, :update? if @tag.update(tag_params.merge(reviewed_at: Time.now.utc)) + log_action_from_change if @tag.saved_changes? + redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg') else @time_period = report_range @@ -33,6 +36,13 @@ module Admin private + def log_action_from_change + action_log = current_account.action_logs.new(action: 'update', target: @tag) + + action_log.recorded_changes = @tag.saved_changes.slice('usable', 'trendable', 'listable').transform_values(&:last) + action_log.save + end + def set_tag @tag = Tag.find(params[:id]) end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 76edb965a6a..592bc356553 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -37,9 +37,29 @@ module Admin::ActionLogsHelper end when 'Relay' link_to log.human_identifier, admin_relays_path + when 'Tag' + link_to log.human_identifier, admin_tag_path(log.target_id) end end + def chain_multiple_translations(action_log) + case action_log.target_type + when 'Tag' + %i(usable trendable listable).filter_map do |key| + fetch_key = permutation_of_key(action_log, key) + next if fetch_key.nil? + + t "admin.trends.tags.#{fetch_key}" + end.join('; ') + end + end + + def permutation_of_key(log, key) + return if log.public_send(key).nil? + + log.public_send(key) ? key : :"not_#{key}" + end + def sorted_action_log_types Admin::ActionLogFilter::ACTION_TYPE_MAP .keys diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb index c32713bd0d3..f5dcff1834c 100644 --- a/app/models/admin/action_log.rb +++ b/app/models/admin/action_log.rb @@ -19,6 +19,8 @@ # class Admin::ActionLog < ApplicationRecord + LOG_ATTRIBUTES = %i(usable trendable listable).freeze + belongs_to :account belongs_to :target, polymorphic: true, optional: true @@ -26,6 +28,8 @@ class Admin::ActionLog < ApplicationRecord before_validation :set_route_param before_validation :set_permalink + store_accessor :recorded_changes, *LOG_ATTRIBUTES + scope :latest, -> { order(id: :desc) } def action diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index b912865cc85..deb80a93253 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -6,6 +6,7 @@ class Admin::ActionLogFilter account_id target_account_id target_domain + target_tag ).freeze INSTANCE_TARGET_TYPES = %w( @@ -77,6 +78,7 @@ class Admin::ActionLogFilter update_user_role: { target_type: 'UserRole', action: 'update' }.freeze, update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze, unblock_email_account: { target_type: 'Account', action: 'unblock_email' }.freeze, + update_tag: { target_type: 'Tag', action: 'update' }.freeze, create_username_block: { target_type: 'UsernameBlock', action: 'create' }.freeze, update_username_block: { target_type: 'UsernameBlock', action: 'update' }.freeze, destroy_username_block: { target_type: 'UsernameBlock', action: 'destroy' }.freeze, @@ -116,6 +118,8 @@ class Admin::ActionLogFilter when 'target_domain' normalized_domain = TagManager.instance.normalize_domain(value) latest_action_logs.where(human_identifier: normalized_domain, target_type: INSTANCE_TARGET_TYPES) + when 'target_tag' + latest_action_logs.where(human_identifier: value) else raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end diff --git a/app/models/tag.rb b/app/models/tag.rb index bc0ccdfb58f..ff0540cae2c 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -111,6 +111,10 @@ class Tag < ApplicationRecord @history ||= Trends::History.new('tags', id) end + def to_log_human_identifier + formatted_name + end + class << self def find_or_create_by_names(name_or_names) names = Array(name_or_names).map { |str| [normalize_value_for(:name, str), str] }.uniq(&:first) diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml index dcc33318092..2032a7ace31 100644 --- a/app/views/admin/action_logs/_action_log.html.haml +++ b/app/views/admin/action_logs/_action_log.html.haml @@ -4,8 +4,11 @@ = image_tag action_log.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' .log-entry__content .log-entry__title - = t "admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}_html", + = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}_html", name: content_tag(:span, action_log.account.username, class: 'username'), - target: content_tag(:span, log_target(action_log), class: 'target') + target: content_tag(:span, log_target(action_log), class: 'target')) + - if action_log.recorded_changes.present? + = t(chain_multiple_translations(action_log)) + .log-entry__timestamp %time.formatted{ datetime: action_log.created_at.iso8601 } diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml index 0c8b2eaec0b..2096ed17bc7 100644 --- a/app/views/admin/tags/show.html.haml +++ b/app/views/admin/tags/show.html.haml @@ -90,3 +90,14 @@ .actions = f.button :button, t('generic.save_changes'), type: :submit + +- if @tag.persisted? + %hr.spacer/ + + %h3= t('admin.instances.audit_log.title') + - if @action_logs.blank? + %p= t('accounts.nothing_here') + - else + .report-notes + = render partial: 'admin/action_logs/action_log', collection: @action_logs + = link_to t('admin.instances.audit_log.view_all'), admin_action_logs_path(target_tag: @tag.formatted_name), class: 'button' diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index d23cdd33dae..e69da74f1e1 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -243,6 +243,7 @@ en-GB: update_ip_block: Update IP rule update_report: Update Report update_status: Update Post + update_tag: Update Hashtag update_user_role: Update Role update_username_block: Update Username Rule actions: @@ -313,6 +314,7 @@ en-GB: update_ip_block_html: "%{name} changed rule for IP %{target}" update_report_html: "%{name} updated report %{target}" update_status_html: "%{name} updated post by %{target}" + update_tag_html: "%{name} changed %{target} settings to: " update_user_role_html: "%{name} changed %{target} role" update_username_block_html: "%{name} updated rule for usernames containing %{target}" deleted_account: deleted account diff --git a/config/locales/en.yml b/config/locales/en.yml index e803e5c3ef1..726e5adb919 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -289,7 +289,11 @@ en: enable_relay_html: "%{name} enabled the relay %{target}" enable_sign_in_token_auth_user_html: "%{name} enabled email token authentication for %{target}" enable_user_html: "%{name} enabled login for user %{target}" + listable_tag_html: can be suggested memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page" + not_listable_tag_html: won't be suggested + not_trendable_tag_html: won't appear under trends + not_usable_tag_html: cannot be used promote_user_html: "%{name} promoted user %{target}" publish_terms_of_service_html: "%{name} published updates to the terms of service" reject_appeal_html: "%{name} rejected moderation decision appeal from %{target}" @@ -302,6 +306,7 @@ en: sensitive_account_html: "%{name} marked %{target}'s media as sensitive" silence_account_html: "%{name} limited %{target}'s account" suspend_account_html: "%{name} suspended %{target}'s account" + trendable_tag_html: can appear under trends unassigned_report_html: "%{name} unassigned report %{target}" unblock_email_account_html: "%{name} unblocked %{target}'s email address" unsensitive_account_html: "%{name} unmarked %{target}'s media as sensitive" @@ -314,8 +319,10 @@ en: update_ip_block_html: "%{name} changed rule for IP %{target}" update_report_html: "%{name} updated report %{target}" update_status_html: "%{name} updated post by %{target}" + update_tag_html: "%{name} changed %{target} settings to: " update_user_role_html: "%{name} changed %{target} role" update_username_block_html: "%{name} updated rule for usernames containing %{target}" + usable_tag_html: can be used; deleted_account: deleted account empty: No logs found. filter_by_action: Filter by action diff --git a/spec/helpers/admin/action_logs_helper_spec.rb b/spec/helpers/admin/action_logs_helper_spec.rb new file mode 100644 index 00000000000..80075f76759 --- /dev/null +++ b/spec/helpers/admin/action_logs_helper_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Admin::ActionLogsHelper do + before { sign_in Fabricate(:admin_user) } + + describe 'with Tags' do + let(:tag) { Fabricate(:tag, name: '#supertag') } + let(:account) { Fabricate(:account) } + let!(:log) { Fabricate(:action_log, target: tag, account: account, usable: false, listable: true) } + + describe '#permutation_of_key' do + it 'returns different permutations for all different states' do + expect(helper.permutation_of_key(log, :usable)).to eq(:not_usable) + expect(helper.permutation_of_key(log, :trendable)).to be_nil + expect(helper.permutation_of_key(log, :listable)).to eq(:listable) + end + end + + describe '#chain_multiple_translations' do + it 'returns translation keys for all different states' do + expect(helper.chain_multiple_translations(log)).to eq('Cannot be used; Can be suggested') + end + end + end +end diff --git a/spec/requests/admin/tags_spec.rb b/spec/requests/admin/tags_spec.rb index 653c5bd9356..cca633d83f1 100644 --- a/spec/requests/admin/tags_spec.rb +++ b/spec/requests/admin/tags_spec.rb @@ -3,9 +3,9 @@ require 'rails_helper' RSpec.describe 'Admin Tags' do - describe 'PUT /admin/tags/:id' do - before { sign_in Fabricate(:admin_user) } + before { sign_in Fabricate(:admin_user) } + describe 'PUT /admin/tags/:id' do let(:tag) { Fabricate :tag } it 'gracefully handles invalid nested params' do @@ -15,4 +15,17 @@ RSpec.describe 'Admin Tags' do .to have_http_status(400) end end + + describe 'update tag' do + let(:tag) { Fabricate :tag, name: '#supertag' } + + it 'redirects to tag page and saves update log action for all attribute statuses' do + put admin_tag_path(tag.id, params: { tag: { trendable: true, listable: false } }) + + expect(response).to have_http_status(302) + expect(Admin::ActionLog.last.human_identifier).to eq('#supertag') + expect(Admin::ActionLog.pluck(:action)).to eq(%w(update)) + expect(Admin::ActionLog.pluck(:recorded_changes)).to eq([{ 'listable' => false, 'trendable' => true }]) + end + end end