Add batch remove for collections in reports (#39020)

This commit is contained in:
Pia B. 2026-05-18 16:53:40 +02:00 committed by GitHub
parent 2402730083
commit 07a05e1edf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 85 additions and 15 deletions

View File

@ -20,7 +20,7 @@ module Admin
def batch def batch
authorize [:admin, :collection], :index? authorize [:admin, :collection], :index?
@collection_batch_action = Admin::CollectionBatchAction.new(admin_collection_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: 'report')) @collection_batch_action = Admin::CollectionBatchAction.new(admin_collection_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button))
@collection_batch_action.save! @collection_batch_action.save!
rescue ActionController::ParameterMissing rescue ActionController::ParameterMissing
@ -57,5 +57,13 @@ module Admin
def set_collections def set_collections
@collections = @account.collections.includes(accepted_collection_items: :account).page(params[:page]).per(PER_PAGE) @collections = @account.collections.includes(accepted_collection_items: :account).page(params[:page]).per(PER_PAGE)
end end
def action_from_button
if params[:report]
'report'
elsif params[:remove_from_report]
'remove_from_report'
end
end
end end
end end

View File

@ -16,6 +16,8 @@ module Admin
@report_notes = @report.notes.chronological.includes(:account) @report_notes = @report.notes.chronological.includes(:account)
@action_logs = @report.history.includes(:target) @action_logs = @report.history.includes(:target)
@form = Admin::StatusBatchAction.new @form = Admin::StatusBatchAction.new
@collection_form = Admin::CollectionBatchAction.new
@collections = @report.collections
@statuses = @report.statuses.with_includes @statuses = @report.statuses.with_includes
end end

View File

@ -69,8 +69,9 @@ on('change', '#batch_checkbox_all', ({ target }) => {
'.batch-table__select-all', '.batch-table__select-all',
); );
document target
.querySelectorAll<HTMLInputElement>(batchCheckboxClassName) .closest('.batch-table')
?.querySelectorAll<HTMLInputElement>(batchCheckboxClassName)
.forEach((content) => { .forEach((content) => {
content.checked = target.checked; content.checked = target.checked;
}); });
@ -112,17 +113,20 @@ on('click', '.batch-table__select-all button', () => {
} }
}); });
on('change', batchCheckboxClassName, () => { on('change', batchCheckboxClassName, (event) => {
const checkAllElement = document.querySelector<HTMLInputElement>( const targetTable = (event.target as HTMLElement).closest('.batch-table');
if (!targetTable) return;
const checkAllElement = targetTable.querySelector<HTMLInputElement>(
'input#batch_checkbox_all', 'input#batch_checkbox_all',
); );
const selectAllMatchingElement = document.querySelector( const selectAllMatchingElement = targetTable.querySelector(
'.batch-table__select-all', '.batch-table__select-all',
); );
if (checkAllElement) { if (checkAllElement) {
const allCheckboxes = Array.from( const allCheckboxes = Array.from(
document.querySelectorAll<HTMLInputElement>(batchCheckboxClassName), targetTable.querySelectorAll<HTMLInputElement>(batchCheckboxClassName),
); );
checkAllElement.checked = allCheckboxes.every((content) => content.checked); checkAllElement.checked = allCheckboxes.every((content) => content.checked);
checkAllElement.indeterminate = checkAllElement.indeterminate =

View File

@ -13,7 +13,12 @@ class Admin::CollectionBatchAction < Admin::BaseAction
def process_action! def process_action!
return unless collection_ids return unless collection_ids
add_to_report! case type
when 'report'
add_to_report!
when 'remove_from_report'
handle_remove_from_report!
end
end end
def add_to_report! def add_to_report!
@ -24,6 +29,13 @@ class Admin::CollectionBatchAction < Admin::BaseAction
@report_id = @report.id @report_id = @report.id
end end
def handle_remove_from_report!
return unless with_report?
@report.collections = (@report.collections - selected_collections)
@report.save!
end
def selected_collections def selected_collections
@report.target_account.collections.where(id: collection_ids) @report.target_account.collections.where(id: collection_ids)
end end

View File

@ -63,23 +63,26 @@
= render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f } = render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f }
- if Mastodon::Feature.collections_enabled? - if Mastodon::Feature.collections_enabled?
%details{ open: @report.collections.any? } %details{ open: @collections.any? }
%summary %summary
= t 'admin.reports.collections', count: @report.collections.size = t 'admin.reports.collections', count: @collections.size
= form_with model: @form, url: batch_admin_account_collections_path(@report.target_account_id, report_id: @report.id) do |f| = form_with model: @collection_form, url: batch_admin_account_collections_path(@report.target_account_id, report_id: @report.id) do |f|
.batch-table .batch-table
.batch-table__toolbar .batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all %label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions .batch-table__toolbar__actions
= link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]), = link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]),
admin_account_collections_path(@report.target_account_id, report_id: @report.id), admin_account_collections_path(@report.target_account_id, report_id: @report.id),
class: 'table-action-link' class: 'table-action-link'
- if !@collections.empty? && @report.unresolved?
= f.button safe_join([material_symbol('close'), t('admin.collections.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit
.batch-table__body .batch-table__body
- if @report.collections.empty? - if @collections.empty?
= nothing_here 'nothing-here--under-tabs' = nothing_here 'nothing-here--under-tabs'
- else - else
= render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection, locals: { f: f } = render partial: 'admin/shared/collection_batch_row', collection: @collections, as: :collection, locals: { f: f }
- if @report.unresolved? - if @report.unresolved?
%hr.spacer/ %hr.spacer/

View File

@ -349,6 +349,7 @@ en:
back_to_report: Back to report page back_to_report: Back to report page
batch: batch:
add_to_report: 'Add to report #%{id}' add_to_report: 'Add to report #%{id}'
remove_from_report: Remove from report
report: Report report: Report
collection_title: Collection by %{name} collection_title: Collection by %{name}
contents: Contents contents: Contents

View File

@ -46,5 +46,15 @@ RSpec.describe Admin::CollectionBatchAction do
expect(Report.last.collections).to include(collection) expect(Report.last.collections).to include(collection)
end end
end end
context 'when type is remove_from_report' do
let(:report_id) { report.id }
let(:type) { 'remove_from_report' }
it 'does not remove report but removes collection' do
expect { subject.save! }.to change { report.reload.collections }.to([])
.and not_change(Report, :count)
end
end
end end
end end

View File

@ -6,7 +6,8 @@ RSpec.describe 'Admin Collections' do
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
let(:other_account) { Fabricate(:account) } let(:other_account) { Fabricate(:account) }
let(:collection) { Fabricate(:collection, account: account) } let(:collection) { Fabricate(:collection, account: account) }
let!(:second_collection) { Fabricate(:collection, account: account) } let(:unreported_collection) { Fabricate(:collection, account: account) }
let(:second_collection) { Fabricate(:collection, account: account) }
let(:report) { Fabricate(:report, account: other_account, target_account: account) } let(:report) { Fabricate(:report, account: other_account, target_account: account) }
before do before do
@ -32,20 +33,49 @@ RSpec.describe 'Admin Collections' do
end end
describe 'POST /admin/accounts/:account_id/collections/batch' do describe 'POST /admin/accounts/:account_id/collections/batch' do
subject { post batch_admin_account_collections_path(account_id: account.id, report_id: report_id, admin_collection_batch_action: { collection_ids: [collection.id, second_collection.id] }) } subject { post batch_admin_account_collections_path(:account_id => account.id, :report_id => report_id, action_from_button => '', :admin_collection_batch_action => { collection_ids: [collection.id, second_collection.id] }) }
context 'with a valid report' do context 'with a valid report' do
let(:report_id) { report.id } let(:report_id) { report.id }
let(:action_from_button) { 'report' }
it 'redirects to the report page' do it 'redirects to the report page' do
report.collections << collection report.collections << collection
subject subject
expect(response).to redirect_to(admin_report_path(report.id)) expect(response).to redirect_to(admin_report_path(report.id))
expect(response.body).to be_empty
expect(response).to have_http_status(302)
end end
end end
context 'with an invalid report' do context 'with an invalid report' do
let(:report_id) { nil } let(:report_id) { nil }
let(:action_from_button) { 'report' }
it 'redirects to the account collections page' do
subject
expect(response).to redirect_to(admin_account_collections_path(account.id))
expect(response).to have_http_status(302)
end
end
context 'with valid remove_from_report' do
let(:report_id) { report.id }
let(:action_from_button) { 'remove_from_report' }
before do
report.collections << [second_collection, unreported_collection]
end
it 'redirects to the account collections page' do
subject
expect(response).to redirect_to(admin_report_path(report.id))
end
end
context 'with invalid remove_from_report' do
let(:report_id) { nil }
let(:action_from_button) { 'remove_from_report' }
it 'redirects to the account collections page' do it 'redirects to the account collections page' do
subject subject