From 61c4aab3e4fae7f20a20ff0bdfbaff5381dd1593 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Wed, 6 Aug 2025 23:10:40 +0200 Subject: [PATCH 01/12] Export seems to work! --- .../settings/exports/filters_controller.rb | 19 +++++++++++++++++++ app/models/export.rb | 8 ++++++++ app/presenters/export_summary.rb | 6 ++++++ app/views/settings/exports/show.html.haml | 4 ++++ config/locales/en.yml | 1 + config/routes/settings.rb | 1 + spec/models/export_spec.rb | 14 ++++++++++++++ 7 files changed, 53 insertions(+) create mode 100644 app/controllers/settings/exports/filters_controller.rb diff --git a/app/controllers/settings/exports/filters_controller.rb b/app/controllers/settings/exports/filters_controller.rb new file mode 100644 index 00000000000..17f1af10e5d --- /dev/null +++ b/app/controllers/settings/exports/filters_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Settings + module Exports + class FiltersController < BaseController + include Settings::ExportControllerConcern + + def index + send_export_file + end + + private + + def export_data + @export.to_filters_csv + end + end + end +end diff --git a/app/models/export.rb b/app/models/export.rb index 6ed9f60c7c8..694873f795f 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -55,6 +55,14 @@ class Export end end + def to_filters_csv + CSV.generate(headers: ['Title', 'Context', 'Keywords', 'Action', 'Expire after'], write_headers: true) do |csv| + account.custom_filters.reorder(id: :desc).each do |filter| + csv << [filter.title, filter.context, filter.keywords.map(&:keyword), filter.action, filter.expires_at] + end + end + end + private def to_csv(accounts) diff --git a/app/presenters/export_summary.rb b/app/presenters/export_summary.rb index 8e45aadf67d..32af0c3302c 100644 --- a/app/presenters/export_summary.rb +++ b/app/presenters/export_summary.rb @@ -10,6 +10,7 @@ class ExportSummary :owned_lists, :media_attachments, :muting, + :custom_filters, to: :account, prefix: true ) @@ -47,6 +48,10 @@ class ExportSummary counts[:muting].value end + def total_filters + counts[:filters].value + end + def total_statuses account.statuses_count end @@ -64,6 +69,7 @@ class ExportSummary domain_blocks: account_domain_blocks.async_count, owned_lists: account_owned_lists.async_count, muting: account_muting.async_count, + filters: account_custom_filters.async_count, storage: account_media_attachments.async_sum(:file_file_size), } end diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml index 5a151be73be..eb1421021cf 100644 --- a/app/views/settings/exports/show.html.haml +++ b/app/views/settings/exports/show.html.haml @@ -40,6 +40,10 @@ %th= t('exports.bookmarks') %td= number_with_delimiter @export_summary.total_bookmarks %td= table_link_to 'download', t('exports.csv'), settings_exports_bookmarks_path(format: :csv) + %tr + %th= t('exports.filters') + %td= number_with_delimiter @export_summary.total_filters + %td= table_link_to 'download', t('exports.csv'), settings_exports_filters_path(format: :csv) %hr.spacer/ diff --git a/config/locales/en.yml b/config/locales/en.yml index 06db5e3cfce..aa0070cff50 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1411,6 +1411,7 @@ en: lists: Lists mutes: You mute storage: Media storage + filters: Filters featured_tags: add_new: Add new errors: diff --git a/config/routes/settings.rb b/config/routes/settings.rb index cefa24316db..27c89b57340 100644 --- a/config/routes/settings.rb +++ b/config/routes/settings.rb @@ -29,6 +29,7 @@ namespace :settings do resources :lists, only: :index resources :domain_blocks, only: :index, controller: :blocked_domains resources :bookmarks, only: :index + resources :filters, only: :index end resources :two_factor_authentication_methods, only: [:index] do diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index 81aaf885855..e9579e90756 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -103,4 +103,18 @@ RSpec.describe Export do ) end end + + describe '#to_filters_csv' do + before { Fabricate.times(2, :custom_filters, account: account) } + + let(:export) { CSV.parse(subject.to_filters_csv) } + + it 'returns a csv of custom filters' do + expect(export) + .to contain_exactly( + include(/statuses/), + include(/statuses/) + ) + end + end end From dbdf20b37345306b505c5c2d3ebe7ed85bdc23e1 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Thu, 7 Aug 2025 18:27:07 +0200 Subject: [PATCH 02/12] Working on import --- .../settings/imports_controller.rb | 2 + app/models/bulk_import.rb | 1 + app/models/form/import.rb | 40 +++++++++++++------ app/services/bulk_import_service.rb | 14 +++++++ app/views/settings/imports/index.html.haml | 2 +- config/locales/en.yml | 7 ++++ spec/models/export_spec.rb | 5 ++- 7 files changed, 56 insertions(+), 15 deletions(-) diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index be1699315f6..eee4cc7feb5 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -13,6 +13,7 @@ class Settings::ImportsController < Settings::BaseController domain_blocking: 'blocked_domains_failures.csv', bookmarks: 'bookmarks_failures.csv', lists: 'lists_failures.csv', + filters: 'filters_failures.csv', }.freeze TYPE_TO_HEADERS_MAP = { @@ -22,6 +23,7 @@ class Settings::ImportsController < Settings::BaseController domain_blocking: false, bookmarks: false, lists: false, + filters: false, }.freeze RECENT_IMPORTS_LIMIT = 10 diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb index e3e46d7b1c6..9b873155616 100644 --- a/app/models/bulk_import.rb +++ b/app/models/bulk_import.rb @@ -34,6 +34,7 @@ class BulkImport < ApplicationRecord domain_blocking: 3, bookmarks: 4, lists: 5, + filters: 6, } enum :state, { diff --git a/app/models/form/import.rb b/app/models/form/import.rb index 3cc4af064ff..f5c812be587 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -19,6 +19,7 @@ class Form::Import domain_blocking: ['#domain'], bookmarks: ['#uri'], lists: ['List name', 'Account address'], + filters: ['Title', 'Context', 'Keywords', 'Action', 'Expire after'], }.freeze KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze @@ -55,6 +56,8 @@ class Form::Import :bookmarks elsif file_name_matches?('lists') :lists + elsif file_name_matches?('filters') || csv_headers_match?('Keywords') + :filters end end @@ -102,6 +105,8 @@ class Form::Import ['#uri'] when :lists ['List name', 'Account address'] + when :filters + ['Title', 'Context', 'Keywords', 'Action', 'Expire after'] end end @@ -109,19 +114,30 @@ class Form::Import return @csv_data if defined?(@csv_data) csv_converter = lambda do |field, field_info| - case field_info.header - when 'Show boosts', 'Notify on new posts', 'Hide notifications' - ActiveModel::Type::Boolean.new.cast(field&.downcase) - when 'Languages' - field&.split(',')&.map(&:strip)&.presence - when 'Account address' - field.strip.gsub(/\A@/, '') - when '#domain' - field&.strip&.downcase - when '#uri', 'List name' - field.strip + if :type == :filters + case field_info.header + when 'Context', 'Keywords' + field&.split(',')&.map(&:strip)&.presence + when 'Expire after' + field.blank? ? nil : Time.zone.parse(field) + else + field + end else - field + case field_info.header + when 'Show boosts', 'Notify on new posts', 'Hide notifications' + ActiveModel::Type::Boolean.new.cast(field&.downcase) + when 'Languages' + field&.split(',')&.map(&:strip)&.presence + when 'Account address' + field.strip.gsub(/\A@/, '') + when '#domain' + field&.strip&.downcase + when '#uri', 'List name' + field.strip + else + field + end end end diff --git a/app/services/bulk_import_service.rb b/app/services/bulk_import_service.rb index a361c7a3dac..471c3f94caf 100644 --- a/app/services/bulk_import_service.rb +++ b/app/services/bulk_import_service.rb @@ -18,6 +18,10 @@ class BulkImportService < BaseService import_bookmarks! when :lists import_lists! + when :filters + import_filters! + else + raise NotImplementedError, "Unknown import type: #{@import.type}" end @import.update!(state: :finished, finished_at: Time.now.utc) if @import.processed_items == @import.total_items @@ -182,4 +186,14 @@ class BulkImportService < BaseService [row.id] end end + + def import_filters! + rows = @import.rows.to_a + + @account.custom_filters.destroy_all if @import.overwrite? + + Import::RowWorker.push_bulk(rows) do |row| + [row.id] + end + end end diff --git a/app/views/settings/imports/index.html.haml b/app/views/settings/imports/index.html.haml index 55421991e13..ec07180326f 100644 --- a/app/views/settings/imports/index.html.haml +++ b/app/views/settings/imports/index.html.haml @@ -5,7 +5,7 @@ .field-group = f.input :type, as: :grouped_select, - collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) }, + collection: { constructive: %i(following bookmarks lists), destructive: %i(filters muting blocking domain_blocking) }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface'), diff --git a/config/locales/en.yml b/config/locales/en.yml index aa0070cff50..5f3ceaa33c6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1517,6 +1517,9 @@ en: muting_html: one: You are about to replace your list of muted account with up to %{count} account from %{filename}. other: You are about to replace your list of muted accounts with up to %{count} accounts from %{filename}. + filters_html: + one: You are about to replace your filters with up to %{count} filter from %{filename}. + other: You are about to replace your filters with up to %{count} filters from %{filename}. preambles: blocking_html: one: You are about to block up to %{count} account from %{filename}. @@ -1536,6 +1539,9 @@ en: muting_html: one: You are about to mute up to %{count} account from %{filename}. other: You are about to mute up to %{count} accounts from %{filename}. + filters_html: + one: You are about to add up to %{count} filter from %{filename}. + other: You are about to add up to %{count} filters from %{filename}. preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking. recent_imports: Recent imports states: @@ -1564,6 +1570,7 @@ en: following: Following list lists: Lists muting: Muting list + filters: Filters upload: Upload invites: delete: Deactivate diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index e9579e90756..16b0f436b36 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -112,8 +112,9 @@ RSpec.describe Export do it 'returns a csv of custom filters' do expect(export) .to contain_exactly( - include(/statuses/), - include(/statuses/) + contain_exactly('Title', 'Context', 'Keywords', 'Action', 'Expire after'), + include('discourse', 'home,notifications', 'discourse', 'warn', be_blank), + include(be_present) ) end end From d8725ea55472f18102e14d02bab1d790e40cc075 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Thu, 7 Aug 2025 22:28:58 +0200 Subject: [PATCH 03/12] Import seems to work! --- app/controllers/settings/imports_controller.rb | 2 ++ app/models/form/import.rb | 12 ++++++++++-- app/services/bulk_import_row_service.rb | 7 +++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index eee4cc7feb5..a53e4ca3740 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -57,6 +57,8 @@ class Settings::ImportsController < Settings::BaseController csv << [row.data['uri']] when :lists csv << [row.data['list_name'], row.data['acct']] + when :filters + csv << [row.data['title'], row.data['context'], row.data['keywords'], row.data['action'], row.data['expires_at']] end end end diff --git a/app/models/form/import.rb b/app/models/form/import.rb index f5c812be587..914cd59f8c0 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -33,6 +33,13 @@ class Form::Import '#domain' => 'domain', '#uri' => 'uri', 'List name' => 'list_name', + + # Filters + 'Title' => 'title', + 'Context' => 'context', + 'Keywords' => 'keywords', + 'Action' => 'action', + 'Expire after' => 'expires_at', }.freeze class EmptyFileError < StandardError; end @@ -114,10 +121,11 @@ class Form::Import return @csv_data if defined?(@csv_data) csv_converter = lambda do |field, field_info| - if :type == :filters + case type.to_sym + when :filters case field_info.header when 'Context', 'Keywords' - field&.split(',')&.map(&:strip)&.presence + Oj.load(field) when 'Expire after' field.blank? ? nil : Time.zone.parse(field) else diff --git a/app/services/bulk_import_row_service.rb b/app/services/bulk_import_row_service.rb index 26909dfe04f..dd6a540aed2 100644 --- a/app/services/bulk_import_row_service.rb +++ b/app/services/bulk_import_row_service.rb @@ -39,6 +39,13 @@ class BulkImportRowService FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id list.accounts << @target_account + when :filters + filter = @account.custom_filters.find_or_initialize_by(title: @data['title']) + filter.context = @data['context'] + filter.keywords = @data['keywords'].map { |keyword| CustomFilterKeyword.new(keyword: keyword) } + filter.action = @data['action'].to_sym + filter.expires_at = @data['expires_at'] + filter.save! end true From 3e6fac42c14d473563b5749f14dc711ef5c27254 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Thu, 7 Aug 2025 23:40:57 +0200 Subject: [PATCH 04/12] Working on tests --- spec/controllers/settings/imports_controller_spec.rb | 12 ++++++++++++ spec/fixtures/files/filters.csv | 0 spec/models/form/import_spec.rb | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 spec/fixtures/files/filters.csv diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index c2c6c353f31..b6aa681e288 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -229,6 +229,18 @@ RSpec.describe Settings::ImportsController do it_behaves_like 'export failed rows', "Amigos,user@example.com\nFrenemies,user@org.org\n" end + + context 'with filters' do + let(:import_type) { 'filters' } + + let(:rows) do + [ + { 'title' => 'Invalid Filter', 'context' => ['waffles'], 'keywords' => ['mean stuff'], 'action' => 'hide', 'expires_at' => nil }, + ] + end + + it_behaves_like 'export failed rows', "Invalid Filter,\"[\"\"waffles\"\"]\",\"[\"\"mean stuff\"\"]\",hide,\n" + end end describe 'POST #create' do diff --git a/spec/fixtures/files/filters.csv b/spec/fixtures/files/filters.csv new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb index d682e13ecb9..3549af6211f 100644 --- a/spec/models/form/import_spec.rb +++ b/spec/models/form/import_spec.rb @@ -282,6 +282,12 @@ RSpec.describe Form::Import do { 'acct' => 'foo@example.com', 'list_name' => 'test' }, ] + it_behaves_like 'on successful import', 'filters', 'merge', 'filters.csv', [ + { 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'foo@example.com', 'list_name' => 'test' }, + ] + # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users # # https://github.com/mastodon/mastodon/issues/20571 From 9c9dd6612918bcdcf1653bf278c182f713b1da12 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Fri, 8 Aug 2025 20:21:36 +0200 Subject: [PATCH 05/12] Whole word import/export --- app/models/export.rb | 4 ++-- app/models/form/import.rb | 5 +++-- app/services/bulk_import_row_service.rb | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/export.rb b/app/models/export.rb index 694873f795f..9a2d7df5c22 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -56,9 +56,9 @@ class Export end def to_filters_csv - CSV.generate(headers: ['Title', 'Context', 'Keywords', 'Action', 'Expire after'], write_headers: true) do |csv| + CSV.generate(headers: ['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'], write_headers: true) do |csv| account.custom_filters.reorder(id: :desc).each do |filter| - csv << [filter.title, filter.context, filter.keywords.map(&:keyword), filter.action, filter.expires_at] + csv << [filter.title, filter.context, filter.keywords.map(&:keyword), filter.keywords.map(&:whole_word), filter.action, filter.expires_at] end end end diff --git a/app/models/form/import.rb b/app/models/form/import.rb index 914cd59f8c0..1f278752478 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -19,7 +19,7 @@ class Form::Import domain_blocking: ['#domain'], bookmarks: ['#uri'], lists: ['List name', 'Account address'], - filters: ['Title', 'Context', 'Keywords', 'Action', 'Expire after'], + filters: ['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'], }.freeze KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze @@ -38,6 +38,7 @@ class Form::Import 'Title' => 'title', 'Context' => 'context', 'Keywords' => 'keywords', + 'Whole Word' => 'whole_word', 'Action' => 'action', 'Expire after' => 'expires_at', }.freeze @@ -124,7 +125,7 @@ class Form::Import case type.to_sym when :filters case field_info.header - when 'Context', 'Keywords' + when 'Context', 'Keywords', 'Whole Word' Oj.load(field) when 'Expire after' field.blank? ? nil : Time.zone.parse(field) diff --git a/app/services/bulk_import_row_service.rb b/app/services/bulk_import_row_service.rb index dd6a540aed2..6ad9b008cce 100644 --- a/app/services/bulk_import_row_service.rb +++ b/app/services/bulk_import_row_service.rb @@ -42,7 +42,7 @@ class BulkImportRowService when :filters filter = @account.custom_filters.find_or_initialize_by(title: @data['title']) filter.context = @data['context'] - filter.keywords = @data['keywords'].map { |keyword| CustomFilterKeyword.new(keyword: keyword) } + filter.keywords = @data['keywords'].map.with_index { |keyword, i| CustomFilterKeyword.new(keyword: keyword, whole_word: @data['whole_word'][i]) } filter.action = @data['action'].to_sym filter.expires_at = @data['expires_at'] filter.save! From 6205d42462e6819fe1209e8ace8da3082d19ea38 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Fri, 8 Aug 2025 20:48:25 +0200 Subject: [PATCH 06/12] Add some tests --- spec/controllers/settings/imports_controller_spec.rb | 4 ++++ spec/fixtures/files/filters.csv | 4 ++++ spec/models/form/import_spec.rb | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index b6aa681e288..589c89dfa55 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -289,6 +289,8 @@ RSpec.describe Settings::ImportsController do it_behaves_like 'successful import', 'domain_blocking', 'domain_blocks.csv', 'overwrite' it_behaves_like 'successful import', 'bookmarks', 'bookmark-imports.txt', 'merge' it_behaves_like 'successful import', 'bookmarks', 'bookmark-imports.txt', 'overwrite' + it_behaves_like 'successful import', 'filters', 'filters.csv', 'merge' + it_behaves_like 'successful import', 'filters', 'filters.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'following', 'domain_blocks.csv', 'merge' it_behaves_like 'unsuccessful import', 'following', 'domain_blocks.csv', 'overwrite' @@ -296,6 +298,8 @@ RSpec.describe Settings::ImportsController do it_behaves_like 'unsuccessful import', 'blocking', 'domain_blocks.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'muting', 'domain_blocks.csv', 'merge' it_behaves_like 'unsuccessful import', 'muting', 'domain_blocks.csv', 'overwrite' + it_behaves_like 'unsuccessful import', 'filters', 'domain_blocks.csv', 'merge' + it_behaves_like 'unsuccessful import', 'filters', 'domain_blocks.csv', 'overwrite' it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'merge' it_behaves_like 'unsuccessful import', 'following', 'empty.csv', 'overwrite' diff --git a/spec/fixtures/files/filters.csv b/spec/fixtures/files/filters.csv index e69de29bb2d..d554c787569 100644 --- a/spec/fixtures/files/filters.csv +++ b/spec/fixtures/files/filters.csv @@ -0,0 +1,4 @@ +Title,Context,Keywords,Whole Word,Action,Expire after +current events,"[""home"", ""public"", ""account""]","[""minions song contest""]",[true],warn,2025-08-14 21:33:29 UTC +unwanted solicitations,"[""notifications"", ""thread""]","[""wizard school enrolment""]",[false],hide, +scary things,"[""home"", ""public""]","[""flying sharks"", ""sapient crabs""]","[true, false]",blur, \ No newline at end of file diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb index 3549af6211f..bb781b49f78 100644 --- a/spec/models/form/import_spec.rb +++ b/spec/models/form/import_spec.rb @@ -283,9 +283,9 @@ RSpec.describe Form::Import do ] it_behaves_like 'on successful import', 'filters', 'merge', 'filters.csv', [ - { 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' }, - { 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' }, - { 'acct' => 'foo@example.com', 'list_name' => 'test' }, + { 'title' => 'current events', 'context' => %w(home public account), 'keywords' => ['minions song contest'], 'whole_word' => [true], 'action' => 'warn', 'expires_at' => Time.zone.parse('2025-08-14 21:33:29 UTC') }, + { 'title' => 'unwanted solicitations', 'context' => %w(notifications thread), 'keywords' => ['wizard school enrolment'], 'whole_word' => [false], 'action' => 'hide', 'expires_at' => nil }, + { 'title' => 'scary things', 'context' => %w(home public), 'keywords' => ['flying sharks', 'sapient crabs'], 'whole_word' => [true, false], 'action' => 'blur', 'expires_at' => nil }, ] # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users From 48c8b48df744b1ef8524894ceb2bc1191c437d16 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Fri, 8 Aug 2025 21:06:36 +0200 Subject: [PATCH 07/12] Add "whole word" column to some places it was missing --- app/controllers/settings/imports_controller.rb | 2 +- app/models/form/import.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index a53e4ca3740..e3c4834aac1 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -58,7 +58,7 @@ class Settings::ImportsController < Settings::BaseController when :lists csv << [row.data['list_name'], row.data['acct']] when :filters - csv << [row.data['title'], row.data['context'], row.data['keywords'], row.data['action'], row.data['expires_at']] + csv << [row.data['title'], row.data['context'], row.data['keywords'], row.data['whole_word'], row.data['action'], row.data['expires_at']] end end end diff --git a/app/models/form/import.rb b/app/models/form/import.rb index 1f278752478..05f638654f3 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -114,7 +114,7 @@ class Form::Import when :lists ['List name', 'Account address'] when :filters - ['Title', 'Context', 'Keywords', 'Action', 'Expire after'] + ['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'] end end From 9474197b43496e75eba534f6eb65ad0a31d45a6a Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Fri, 8 Aug 2025 21:32:15 +0200 Subject: [PATCH 08/12] Partially fix export_spec --- spec/models/export_spec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index 16b0f436b36..f5025fa9b04 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -105,16 +105,15 @@ RSpec.describe Export do end describe '#to_filters_csv' do - before { Fabricate.times(2, :custom_filters, account: account) } + before { Fabricate.times(2, :custom_filter, account: account) } let(:export) { CSV.parse(subject.to_filters_csv) } it 'returns a csv of custom filters' do expect(export) .to contain_exactly( - contain_exactly('Title', 'Context', 'Keywords', 'Action', 'Expire after'), - include('discourse', 'home,notifications', 'discourse', 'warn', be_blank), - include(be_present) + contain_exactly('Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'), + include('discourse', '["home", "notifications"]', '["discourse"]', '[true]', 'warn', be_blank) ) end end From df53fb5bde08fe8cef5b303991e5c7fb8813045a Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Fri, 8 Aug 2025 23:17:02 +0200 Subject: [PATCH 09/12] Fix tests --- spec/controllers/settings/imports_controller_spec.rb | 4 ++-- spec/models/export_spec.rb | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index 589c89dfa55..718b84c828f 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -235,11 +235,11 @@ RSpec.describe Settings::ImportsController do let(:rows) do [ - { 'title' => 'Invalid Filter', 'context' => ['waffles'], 'keywords' => ['mean stuff'], 'action' => 'hide', 'expires_at' => nil }, + { 'title' => 'Invalid Filter', 'context' => ['waffles'], 'keywords' => ['mean stuff'], 'whole_word' => [true], 'action' => 'hide', 'expires_at' => nil }, ] end - it_behaves_like 'export failed rows', "Invalid Filter,\"[\"\"waffles\"\"]\",\"[\"\"mean stuff\"\"]\",hide,\n" + it_behaves_like 'export failed rows', "Invalid Filter,\"[\"\"waffles\"\"]\",\"[\"\"mean stuff\"\"]\",[true],hide,\n" end end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index f5025fa9b04..4652aed4e4c 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -105,7 +105,11 @@ RSpec.describe Export do end describe '#to_filters_csv' do - before { Fabricate.times(2, :custom_filter, account: account) } + before do + Fabricate.times(2, :custom_filter, account: account) do + keywords { [Fabricate(:custom_filter_keyword)] } + end + end let(:export) { CSV.parse(subject.to_filters_csv) } @@ -113,7 +117,8 @@ RSpec.describe Export do expect(export) .to contain_exactly( contain_exactly('Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'), - include('discourse', '["home", "notifications"]', '["discourse"]', '[true]', 'warn', be_blank) + include('discourse', '["home", "notifications"]', '["discourse"]', '[true]', 'warn', be_blank), + include(be_present) ) end end From 6d702922782e8847d9e29f3e82104d0329b497b9 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Sat, 9 Aug 2025 01:45:48 +0200 Subject: [PATCH 10/12] i18n normalize --- config/locales/en.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f3ceaa33c6..0bf2bc51f3c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1408,10 +1408,10 @@ en: bookmarks: Bookmarks csv: CSV domain_blocks: Domain blocks + filters: Filters lists: Lists mutes: You mute storage: Media storage - filters: Filters featured_tags: add_new: Add new errors: @@ -1508,6 +1508,9 @@ en: domain_blocking_html: one: You are about to replace your domain block list with up to %{count} domain from %{filename}. other: You are about to replace your domain block list with up to %{count} domains from %{filename}. + filters_html: + one: You are about to replace your filters with up to %{count} filter from %{filename}. + other: You are about to replace your filters with up to %{count} filters from %{filename}. following_html: one: You are about to follow up to %{count} account from %{filename} and stop following anyone else. other: You are about to follow up to %{count} accounts from %{filename} and stop following anyone else. @@ -1517,9 +1520,6 @@ en: muting_html: one: You are about to replace your list of muted account with up to %{count} account from %{filename}. other: You are about to replace your list of muted accounts with up to %{count} accounts from %{filename}. - filters_html: - one: You are about to replace your filters with up to %{count} filter from %{filename}. - other: You are about to replace your filters with up to %{count} filters from %{filename}. preambles: blocking_html: one: You are about to block up to %{count} account from %{filename}. @@ -1530,6 +1530,9 @@ en: domain_blocking_html: one: You are about to block up to %{count} domain from %{filename}. other: You are about to block up to %{count} domains from %{filename}. + filters_html: + one: You are about to add up to %{count} filter from %{filename}. + other: You are about to add up to %{count} filters from %{filename}. following_html: one: You are about to follow up to %{count} account from %{filename}. other: You are about to follow up to %{count} accounts from %{filename}. @@ -1539,9 +1542,6 @@ en: muting_html: one: You are about to mute up to %{count} account from %{filename}. other: You are about to mute up to %{count} accounts from %{filename}. - filters_html: - one: You are about to add up to %{count} filter from %{filename}. - other: You are about to add up to %{count} filters from %{filename}. preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking. recent_imports: Recent imports states: @@ -1567,10 +1567,10 @@ en: blocking: Blocking list bookmarks: Bookmarks domain_blocking: Domain blocking list + filters: Filters following: Following list lists: Lists muting: Muting list - filters: Filters upload: Upload invites: delete: Deactivate From c6d846cecd02e11b993e8b3f89996706145b8f7c Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Sat, 9 Aug 2025 01:50:07 +0200 Subject: [PATCH 11/12] Order filters in export by title to match filters page --- app/models/export.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/export.rb b/app/models/export.rb index 9a2d7df5c22..9fdb5864752 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -57,7 +57,7 @@ class Export def to_filters_csv CSV.generate(headers: ['Title', 'Context', 'Keywords', 'Whole Word', 'Action', 'Expire after'], write_headers: true) do |csv| - account.custom_filters.reorder(id: :desc).each do |filter| + account.custom_filters.reorder(:title).each do |filter| csv << [filter.title, filter.context, filter.keywords.map(&:keyword), filter.keywords.map(&:whole_word), filter.action, filter.expires_at] end end From 5425419b27c3d0449fd4f9cc72a27d8c93a53f19 Mon Sep 17 00:00:00 2001 From: Evan Summers Date: Sat, 9 Aug 2025 10:43:39 +0200 Subject: [PATCH 12/12] Add filters to ignore_unused for i18n like other import/export --- config/i18n-tasks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index b934696bda6..39b08cc9de4 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -66,8 +66,8 @@ ignore_unused: - 'admin_mailer.*.subject' - 'user_mailer.*.subject' - 'notification_mailer.*' - - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*' - - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*' + - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists,filters}_html.*' + - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists,filters}_html.*' - 'mail_subscriptions.unsubscribe.emails.*' - 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use - 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use