Skip to content

show summary information on confirmation modal - #55

Merged
Fivell merged 13 commits into
activeadmin-plugins:masterfrom
yeti-switch:confirmation_with_summary
Oct 1, 2026
Merged

Fivell merged 13 commits into
activeadmin-plugins:masterfrom
yeti-switch:confirmation_with_summary

Conversation

@dmitry-sinina

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The preview can miscount scoped records, permit confirmation after a failed count, or describe a checkbox change that is not submitted.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

This PR adds affected-record summaries to scoped collection action dialogs, helping administrators review a batch action before confirming it.

Changes:

  • Adds record counts, loading indicators, and an optional second confirmation step.
  • Supports custom classes on form inputs and documents the new options.
  • Adds translations and feature specs for the new dialogs.
File Description
vendor/​assets/​stylesheets/​active_admin_scoped_collection_actions.scss Styles summaries, spinners, and confirmation controls.
vendor/​assets/​javascripts/​lib/​dialog_records_summary.js Loads and displays record counts.
vendor/​assets/​javascripts/​lib/​dialog_mass_fields_update.js Builds the field-change preview.
vendor/​assets/​javascripts/​lib/​dialog_confirm_submit.js Adds the second confirmation step.
vendor/​assets/​javascripts/​active_admin_scoped_collection_actions.js Connects dialogs to count and action requests.
spec/​support/​admin.rb Configures example actions.
spec/​posts_actions_spec.rb Tests update confirmation and counts.
spec/​authors_actions_spec.rb Tests delete summaries and custom inputs.
README.md Documents the new options and inputs.
lib/​active_admin_scoped_collection_actions/​resource_extension.rb Passes confirmation settings to the UI.
lib/​active_admin_scoped_collection_actions/​dsl.rb Adds the record-count action.
config/​locales/​sv.yml Adds Swedish summary text.
config/​locales/​es.yml Adds Spanish summary text.
config/​locales/​en.yml Adds English summary text.
config/​locales/​de.yml Adds German summary text.
CHANGELOG.md Records the new features.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Fivell added 7 commits October 1, 2026 14:41
The :scoped_collection_records_count batch action has no authorized? guard
and its comment claims the authorization scope makes one unnecessary. Cover
both halves: the scope is applied to the count, and the count is served to a
user the action itself refuses.
Every existing spec reads the summary of an unfiltered collection, so the
number itself was never asserted. Cover the current filter, a checked subset
and a scoped_collection that already carries order and limit, which is what
the .except in the count action strips off.
Both options take true/false, a String or a Proc, and only one form of each
was exercised. Adds the String and Proc forms of :confirm_summary, the boolean
fallback of :confirm_submit, and the branch most likely to regress: a Proc
returning false has to leave the data attribute absent, not present and empty.
confirm_summary: true picks confirm_destroy_summary for
:scoped_collection_destroy and confirm_action_summary for anything else. Only
the destroy side of that ternary was exercised.
Pins that the number replaces the placeholder with nothing left over, and that
a summary without a {count} at all still renders and still lets the action be
confirmed.
Catches the next key that lands in en.yml only.
add_scoped_collection_records_count_action runs on every
scoped_collection_action call, four times over for the authors resource. Pins
that this leaves a single batch action and that it is never offered in the
batch actions dropdown.
@Fivell

Fivell commented Oct 1, 2026

Copy link
Copy Markdown
Member

Pushed 7 test-only commits on top of e8572f0. No changes to lib/, config/, vendor/ or the workflow — the diff is spec/ only (+409).

The specs added, in order:

  1. test: pin what the records count action exposes under authorization — spec/records_count_authorization_spec.rb
  2. test: pin the affected record count against filters, selection and scope — spec/records_count_spec.rb
  3. test: cover the remaining :confirm_summary and :confirm_submit forms — spec/confirm_options_spec.rb
  4. test: cover the non-destroy default summary translation
  5. test: cover {count} substitution in the summary
  6. test: assert locale parity across en, de, es and sv — spec/locales_spec.rb
  7. test: assert the count action is registered once and stays hidden

Plus add_option_author_resource / add_ordered_author_resource in spec/support/admin.rb. They register Author under as: names of their own, because ActiveAdmin reuses an already-registered Resource and never clears its scoped_collection_actions hash — changing options on the shared authors resource would leak between examples.

Authorization finding

add_scoped_collection_records_count_action has no authorized? guard, and the comment above it says that is safe because "Collection is already limited by authorization scope". Tested rather than assumed, with a custom ActiveAdmin::AuthorizationAdapter. Both halves are true, and the second one is the part worth a decision:

  • The scope is applied. scoped_collection_records goes through batch_action_collection([:authorization_scope, ...]), so an adapter whose scope_collection narrows the collection to 1 of 3 authors makes the dialog say "1 record(s)". No row the user cannot see on the index page is counted.
  • But an unauthorized user still gets the count. With an adapter that returns false for :batch_destroy, the confirmation dialog reports the full "3 record(s)" and only the subsequent OK is refused. scoped_collection_update/scoped_collection_destroy check authorized?, scoped_collection_records_count does not, and ActiveAdmin::BatchActions::Controller#batch_action does no authorization of its own. if: proc { false } only hides an action from the dropdown; current_batch_action still finds it, so the endpoint is reachable.

So the cardinality of a collection leaks to a user who may not act on it. Whether that matters is your call — deliberately fine for most apps, less so if row counts are themselves sensitive. I have not added a guard: that is a behaviour change for you to make, not something to slip in under a test. The specs pin today's behaviour and will go red if you add one, which is the right kind of red.

Unrelated pre-existing bug spotted

dsl.rb looks the no-permissions flash up as active_admin_scoped_collection_actions.actions.no_permissions_msg, but all four locale files keep no_permissions_msg at the top level, not under actions:. A denied user therefore gets "Translation missing: en.active_admin_scoped_collection_actions.actions.no_permissions_msg". This is on master too and predates this branch, so I left it alone — the spec asserts a flash_error appears and the records survive, without pinning the broken wording (there is a comment saying so).

CI still does not run on this PR

The push updated the PR head to ee5a438, but no workflow started — gh pr checks 55 still says "no checks reported", and the head commit has zero check suites, not even one in action_required. So this is not a pending approval: GitHub is not creating a run at all. The branch lives on the yeti-switch fork and @dmitry-sinina is not an activeadmin-plugins member, which fits "Run workflows from fork pull requests" being off for this repo. That also explains why the original commit from July never got a green tick either.

Nothing a push can fix — it needs either that repo setting changed (Settings > Actions > General > Fork pull request workflows) or the branch mirrored into this repo. Until then the matrix below is all the signal there is.

Verification

Every new spec was checked against broken code: I reverted or flipped the line it covers, confirmed it went red, then restored. The one that caught me out was the "no summary" example — to_not have_css('.dialog_records_summary') passed against broken code because a configured summary is in the DOM but hidden while the spinner waits, so it is now visible: :all. Worth knowing for the sibling assertions in authors_actions_spec.rb:109 and posts_actions_spec.rb:103, which pass for the same visibility reason.

Full suite, 36 examples, 0 failures throughout:

Ruby Rails AA seeds
3.4.10 8.1.4 3.5.2 1234, 4321, 777, 20260101
3.4.10 8.0.5.1 3.5.2 999
3.4.10 7.1.6 3.5.2 555
3.4.10 7.2.3 3.4.0 3690
3.4.10 7.1.6 3.2.0 111
3.3.12 8.1.4 3.5.2 2468

That is every Rails in this branch's matrix (7.1, 7.2, 8.0) plus 8.1, and the oldest and newest ActiveAdmin in it. No order dependence across the seven seeds. bundle install also resolves on Ruby 4.0.6. Ruby 3.2 I could not run — not installed here, and master has already dropped it from the matrix.

Two notes from running it locally, neither caused by this branch:

  • CI=true is required for a local run. rails new writes config.eager_load = ENV["CI"].present? into the dummy app, and without it ActiveAdmin's before_load hooks have not run when a spec calls ActiveAdmin.register, so index pages die in batch_actions.
  • Generating the Rails 8.1 dummy app needs --skip-javascript in tasks/test.rake (8.1 emits stale_when_importmap_changes into ApplicationController, and the dummy app has no importmap-rails). master already has that flag; this branch predates it. The branch's own matrix only goes to Rails 8.0, so its CI is unaffected — it will come in with the merge.

Finally: this branch currently conflicts with master in CHANGELOG.md, and did so before these commits (master has moved on). Leaving that to you rather than rebasing someone else's branch.

Fivell added 4 commits October 1, 2026 15:03
Four real captures of the dummy app: the sidebar, the dialog while the count
is still in flight, the same dialog with the summary resolved, and the result
after confirming.
The repo already used screenshots/ for the two README images; a second directory for the same purpose is one more place to look.
The count action stripped :limit and :offset before counting, on the stated
reasoning that the action goes on to touch the collection in full. Measured
on ActiveRecord 8.0.5.1, it does not: find_each honours both.

  scope .limit(1) over 5 rows   find_each touches 1, stripped count said 5
  scope .offset(3) over 5 rows  find_each touches 2, stripped count said 5

So the dialog promised records the action would never reach. A grouped scope
was worse -- .count answers with a Hash of per-group tallies, which reaches
the dialog as a JSON object and renders as [object Object].

Count through a subquery instead. Every clause survives, a grouped relation
collapses to the number of rows it yields, and the answer is always a single
number. Verified against find_each across plain, limit, offset, limit+offset,
group, where and order+limit scopes: the two agree in every case.

The spec that pinned the old behaviour asserted 3 for a .limit(1) scope. It
now asserts 1, and a grouped resource covers the Hash case. Both fail on the
previous implementation.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Count queries mishandle distinct and eager-loaded scopes, and checkbox confirmations can promise changes that are not submitted.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
Resolved since last review (1)

# with a Hash the dialog would render as [object Object] -- into the
# single number the dialog needs.
scope = scoped_collection_records.except(:eager_load, :select, :order)
count = scope.model.unscoped.from(scope.select('1'), :subquery_for_count).count
# clause and collapses a grouped scope -- whose bare .count answers
# with a Hash the dialog would render as [object Object] -- into the
# single number the dialog needs.
scope = scoped_collection_records.except(:eager_load, :select, :order)
confirm_submit = confirm_submit.call if confirm_submit.is_a?(Proc)
if confirm_submit
b_data[:confirm_submit] = confirm_submit.is_a?(String) ? confirm_submit : I18n.t('active_admin_scoped_collection_actions.confirm_action_message')
b_data[:confirm_submit_summary] = I18n.t('active_admin_scoped_collection_actions.confirm_submit_summary')
The comment above add_grouped_author_resource described order+limit and pointed at /admin/ordered_authors -- it belonged to the neighbouring helper. It was also stale on its own terms: the count action no longer strips limit, only eager_load, select and order. Each helper now describes the relation it actually registers.
@Fivell
Fivell merged commit b1b28b0 into activeadmin-plugins:master Oct 1, 2026
@Fivell
Fivell deleted the confirmation_with_summary branch October 2, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants