Skip to content

[AGILE-362] Make Backlogs destination actions batch-aware - #24779

Open
myabc wants to merge 18 commits into
implementation/AGILE-278-batch-movefrom
implementation/AGILE-362-batch-destinations
Open

[AGILE-362] Make Backlogs destination actions batch-aware#24779
myabc wants to merge 18 commits into
implementation/AGILE-278-batch-movefrom
implementation/AGILE-362-batch-destinations

Conversation

@myabc

@myabc myabc commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Note

This PR is part of a stack. Please review and merge in this order: #24525#24778 → this PR.

Ticket

https://community.openproject.org/wp/AGILE-362

What are you trying to accomplish?

Make the Backlogs inbox, sprint, and backlog-bucket destination actions operate on the current ordered action scope while preserving existing one-card behavior.

The change:

  • loads and authorizes destination intersections server-side
  • submits ordered batches through the atomic collection move contract
  • serializes destination lifecycle changes and concurrent appends so availability checks and placement stay atomic
  • names every affected work package in the move dialogs, so a batch assembled by rubber band or shift-click can be checked before it is committed
  • adds plural announcements, busy-state recovery — including refusing Escape while a move is pending — and end-to-end coverage for selection, confined destinations, stale menus, and atomic rejection

The dialog listing delivers the "list affected elements" half of AGILE-374 for the two move dialogs.

Screenshots

Move to sprint
Screenshot 2026-08-20 at 12 20 41

|

What approach did you choose and why?

The client projects the live action scope onto the existing menu, but the server remains authoritative: collection dialog routes reload every submitted work package, intersect available destinations, and validate the chosen target again under the movement lock. Direct inbox and dialog moves share the existing atomic batch service.

Destination model advisory locks coordinate batch placement with sprint lifecycle operations, and a stable project/destination lock serializes concurrent append placement. The client keeps selection until successful reconciliation and restores its busy state on every result.

The dialogs reuse the treatment WorkPackages::DeleteDialogComponent already gives its descendant list — an inset box of WorkPackages::InfoLineComponent rows — behind a shared Backlogs::SelectedWorkPackagesComponent. The list is a sibling of the form rather than a field inside it, and the dialog's own scrolling body carries a long selection. The selected count moves to a hidden element that the destination select keeps describing itself by, so replacing the visible copy costs assistive technology nothing.

Follow-up work:

  • AGILE-363 restores Move to position for supported selected blocks
  • AGILE-364 applies the shared batch action-menu policy uniformly before every menu opens
  • AGILE-374 covers the remaining dialog-clarity work: the cancel and finish-sprint dialogs, and the copy rewrites
  • AGILE-393 addresses application-wide handling of unsuccessful non-422 Turbo Stream responses

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)

@myabc myabc changed the title implementation/AGILE 362 batch destinations [AGILE-362] Make Backlogs destination actions batch-aware Aug 16, 2026
@myabc
myabc requested a lite review from Copilot August 16, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR makes Backlogs destination actions (move to sprint/bucket/inbox) operate on the current ordered action scope (single card or batch selection), while keeping the server authoritative via destination intersection + revalidation under advisory locking.

Changes:

  • Introduces server-side destination intersection (DestinationAvailability) and updates dialog + menu endpoints to use ordered ids[] batches.
  • Extends the atomic batch move service with destination lifecycle + append serialization locks to keep placement and availability checks atomic under concurrency.
  • Updates the sortable-lists Stimulus stack to project destination candidates, freeze/submit action scopes, handle direct destination moves, and clear selection on successful move events.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
modules/backlogs/spec/support/pages/backlog.rb Adds feature helper APIs for batch selection, dialogs, and announcements.
modules/backlogs/spec/services/backlogs/work_packages/destination_availability_spec.rb Unit coverage for destination intersection/authorization semantics.
modules/backlogs/spec/services/backlogs/work_packages/batch_update_service_spec.rb Extends batch move specs for new locking and revalidation behavior.
modules/backlogs/spec/services/backlogs/work_packages/batch_update_service_concurrency_spec.rb Adds concurrency specs validating serialization/locking guarantees.
modules/backlogs/spec/routing/backlogs/work_packages_routing_spec.rb Updates routing for dialog endpoints to collection POST routes.
modules/backlogs/spec/requests/work_packages/move_collection_spec.rb Adds request coverage for plural announcements + dialog staleness rejection.
modules/backlogs/spec/features/work_packages/batch_destination_menu_spec.rb End-to-end Selenium coverage for batch destination menus/dialogs.
modules/backlogs/spec/controllers/backlogs/work_packages_controller_spec.rb Updates controller specs for new dialog contracts + candidate passing.
modules/backlogs/spec/components/backlogs/work_package_card_menu_component_spec.rb Updates menu component spec for destination metadata + POST dialog loading.
modules/backlogs/spec/components/backlogs/move_to_sprint_dialog_component_spec.rb Updates dialog component spec to submit ordered ids[] collection.
modules/backlogs/spec/components/backlogs/move_to_bucket_dialog_component_spec.rb Updates dialog component spec to submit ordered ids[] collection.
modules/backlogs/config/routes.rb Moves sprint/bucket dialog routes to collection post.
modules/backlogs/config/locales/en.yml Adds no-destination messages and plural moved announcements.
modules/backlogs/app/views/backlogs/backlog/show.html.erb Clears selection on successful move events at the root controller.
modules/backlogs/app/services/backlogs/work_packages/destination_availability.rb New service intersecting destinations across a batch + permissions.
modules/backlogs/app/services/backlogs/work_packages/batch_update_service.rb Adds destination lifecycle + append locks and revalidates via DestinationAvailability.
modules/backlogs/app/controllers/backlogs/work_packages_controller.rb Dialog endpoints now load/authorize ordered collections and render plural announcements.
modules/backlogs/app/components/backlogs/work_package_card_menu_component.rb Menu now receives destination candidate ids and emits destination metadata.
modules/backlogs/app/components/backlogs/work_package_card_menu_component.html.erb Renders destination actions as projected items with dialog POST forms and metadata.
modules/backlogs/app/components/backlogs/move_to_sprint_dialog_component.rb Refactors dialog component to accept work package collections + controller-provided sprints.
modules/backlogs/app/components/backlogs/move_to_sprint_dialog_component.html.erb Submits ids[] and shows selected count in the sprint dialog.
modules/backlogs/app/components/backlogs/move_to_bucket_dialog_component.rb Refactors dialog component to accept work package collections + controller-provided buckets.
modules/backlogs/app/components/backlogs/move_to_bucket_dialog_component.html.erb Submits ids[] and shows selected count in the bucket dialog.
frontend/src/stimulus/controllers/dynamic/sortable-lists/selection.ts Adds ordered selected elements helper for action-scope projection.
frontend/src/stimulus/controllers/dynamic/sortable-lists/selection.spec.ts Unit coverage for ordered selected element resolution.
frontend/src/stimulus/controllers/dynamic/sortable-lists/selection-orchestrator.ts Adds action-scope APIs (prospective vs mutating) for menu/dialog invocation.
frontend/src/stimulus/controllers/dynamic/sortable-lists/selection-orchestrator.spec.ts Covers action-scope semantics and anchor preservation.
frontend/src/stimulus/controllers/dynamic/sortable-lists/scrollable.controller.spec.ts Updates root mocks for new destination/action-scope interface.
frontend/src/stimulus/controllers/dynamic/sortable-lists/list.controller.spec.ts Updates root mocks for new destination/action-scope interface.
frontend/src/stimulus/controllers/dynamic/sortable-lists/list-dom.ts Adds destination identity + intersection helpers for client-side projection.
frontend/src/stimulus/controllers/dynamic/sortable-lists/list-dom.spec.ts Unit coverage for destination intersection and occupancy filtering.
frontend/src/stimulus/controllers/dynamic/sortable-lists/item.controller.ts Projects destination items, freezes ids[] into dialog forms, and supports direct destination moves.
frontend/src/stimulus/controllers/dynamic/sortable-lists/item.controller.spec.ts Extensive coverage for projection, dialog freezing, and destination activation.
frontend/src/stimulus/controllers/dynamic/sortable-lists/drag-and-drop.ts Extends root interface with action-scope + destination move capabilities.
frontend/src/stimulus/controllers/dynamic/sortable-lists.controller.ts Implements destination availability, direct destination moves, and busy-state recovery.
frontend/src/stimulus/controllers/dynamic/sortable-lists.controller.spec.ts Covers action scopes, direct destination submission, and busy-state reconciliation.
frontend/src/stimulus/controllers/async-dialog.controller.ts Reimplements async dialog loading using @rails/request.js + beforeLoad hook.
frontend/src/stimulus/controllers/async-dialog.controller.spec.ts New unit coverage for beforeLoad behavior, loading state, and response handling.
Suppressed comments (2)

frontend/src/stimulus/controllers/async-dialog.controller.ts:49

  • Same as the click path: key activation uses void this.triggerTurboStream() without a catch, which can lead to unhandled promise rejections on request failures or unexpected responses. Catch/log in the handler to fail gracefully.
  private readonly handleKeydown = (event:Event):void => {
    const keyboardEvent = event as KeyboardEvent;
    if (keyboardEvent.key === 'Enter' || keyboardEvent.key === ' ') {
      event.preventDefault();
      void this.triggerTurboStream();

frontend/src/stimulus/controllers/async-dialog.controller.ts:106

  • handleOpenDialog also calls triggerTurboStream() via void ... without handling rejections. If the request fails, this can likewise create an unhandled promise rejection. Catch/log here as well (or handle errors centrally in triggerTurboStream).
  handleOpenDialog(event:CustomEvent<{ url:string }>):void {
    // Trigger the dialog with custom URL
    void this.triggerTurboStream(event.detail.url);
  }

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

Comment thread frontend/src/stimulus/controllers/async-dialog.controller.ts
Comment thread modules/backlogs/app/services/backlogs/work_packages/batch_update_service.rb Outdated
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 5895d3e to 9c1af86 Compare August 16, 2026 23:39
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from f800d44 to d7e10f7 Compare August 17, 2026 12:19
@myabc myabc added feature javascript Pull requests that update Javascript code DO NOT MERGE labels Aug 17, 2026
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./modules/backlogs/spec/features/inbox_column_spec.rb[1:7:1]
  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]
🤖 Ask Copilot to investigate

Copy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer.

@copilot The following spec(s) are flaky in CI (first seen on PR #24779, linked for reference only):

- `rspec ./modules/backlogs/spec/features/inbox_column_spec.rb[1:7:1]`
- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]`

Treat this as a standalone task, unrelated to PR #24779. Create a new branch from origin/dev and open a new pull request targeting dev — do not stack it on PR #24779 or reuse that branch.

Follow the playbook in docs/development/testing/handling-flaky-tests/README.md to find the root cause and fix the underlying race — do not skip, delete, or weaken the spec to make it pass; disabling is a last resort per the playbook, and only with a bug ticket. Verify the fix by running the spec(s) repeatedly (e.g. `script/bulk_run_rspec --run-count 10`).

If you cannot reproduce the flake or are not confident in a fix after reasonable investigation, do not fabricate a change or skip the spec to force CI green. Instead, leave the pull request in draft and document what you tried, the suspected cause, and any leads in its description, then assign @myabc to take over.

Once the fix is verified, title the PR after the spec(s) it fixes, and use the PR description to explain the root cause, how the change resolves it, and the before/after results. Label the PR `flaky-spec`, assign @myabc, and request a review from @myabc.
On every commit, set @myabc as the sole co-author with a `Co-authored-by:` trailer (use their GitHub no-reply email so it links to their account), so it is traceable who dispatched the fix.

@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from d7e10f7 to 7a8768a Compare August 18, 2026 13:40
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./modules/backlogs/spec/features/inbox_column_spec.rb[1:7:1]
  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:1]
  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]
🤖 Ask Copilot to investigate

Copy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer.

@copilot The following spec(s) are flaky in CI (first seen on PR #24779, linked for reference only):

- `rspec ./modules/backlogs/spec/features/inbox_column_spec.rb[1:7:1]`
- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:1]`
- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]`

Treat this as a standalone task, unrelated to PR #24779. Create a new branch from origin/dev and open a new pull request targeting dev — do not stack it on PR #24779 or reuse that branch.

Follow the playbook in docs/development/testing/handling-flaky-tests/README.md to find the root cause and fix the underlying race — do not skip, delete, or weaken the spec to make it pass; disabling is a last resort per the playbook, and only with a bug ticket. Verify the fix by running the spec(s) repeatedly (e.g. `script/bulk_run_rspec --run-count 10`).

If you cannot reproduce the flake or are not confident in a fix after reasonable investigation, do not fabricate a change or skip the spec to force CI green. Instead, leave the pull request in draft and document what you tried, the suspected cause, and any leads in its description, then assign @myabc to take over.

Once the fix is verified, title the PR after the spec(s) it fixes, and use the PR description to explain the root cause, how the change resolves it, and the before/after results. Label the PR `flaky-spec`, assign @myabc, and request a review from @myabc.
On every commit, set @myabc as the sole co-author with a `Co-authored-by:` trailer (use their GitHub no-reply email so it links to their account), so it is traceable who dispatched the fix.

@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 7a8768a to d7e10f7 Compare August 18, 2026 16:52
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from d7e10f7 to fdc3e4e Compare August 18, 2026 17:37
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./modules/backlogs/spec/features/inbox_column_spec.rb[1:7:1]
  • rspec ./spec/features/projects/lists/filters_spec.rb[1:6:1]
  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:1]
  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]
🤖 Ask Copilot to investigate

Copy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer.

@copilot The following spec(s) are flaky in CI (first seen on PR #24779, linked for reference only):

- `rspec ./modules/backlogs/spec/features/inbox_column_spec.rb[1:7:1]`
- `rspec ./spec/features/projects/lists/filters_spec.rb[1:6:1]`
- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:1]`
- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]`

Treat this as a standalone task, unrelated to PR #24779. Create a new branch from origin/dev and open a new pull request targeting dev — do not stack it on PR #24779 or reuse that branch.

Follow the playbook in docs/development/testing/handling-flaky-tests/README.md to find the root cause and fix the underlying race — do not skip, delete, or weaken the spec to make it pass; disabling is a last resort per the playbook, and only with a bug ticket. Verify the fix by running the spec(s) repeatedly (e.g. `script/bulk_run_rspec --run-count 10`).

If you cannot reproduce the flake or are not confident in a fix after reasonable investigation, do not fabricate a change or skip the spec to force CI green. Instead, leave the pull request in draft and document what you tried, the suspected cause, and any leads in its description, then assign @myabc to take over.

Once the fix is verified, title the PR after the spec(s) it fixes, and use the PR description to explain the root cause, how the change resolves it, and the before/after results. Label the PR `flaky-spec`, assign @myabc, and request a review from @myabc.
On every commit, set @myabc as the sole co-author with a `Co-authored-by:` trailer (use their GitHub no-reply email so it links to their account), so it is traceable who dispatched the fix.

@myabc
myabc marked this pull request as ready for review August 18, 2026 22:23
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from fdc3e4e to 41fa8e2 Compare August 19, 2026 12:54
@myabc
myabc requested a balanced review from Copilot August 19, 2026 13:06
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 41fa8e2 to 65025d0 Compare August 19, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 1 comment.

Comment on lines +242 to +247
availability = Backlogs::WorkPackages::DestinationAvailability.new(
project: batch_project,
user:,
work_packages: WorkPackage.where(id: work_packages.map(&:id)).to_a
)
raise BatchFailure, unavailable_target_failure unless availability.permitted?(target)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same issue as #24778 (comment)

@myabc myabc added the ruby Pull requests that update Ruby code label Aug 20, 2026
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:1]
  • rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]
🤖 Ask Copilot to investigate

Copy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer.

@copilot The following spec(s) are flaky in CI (first seen on PR #24779, linked for reference only):

- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:1]`
- `rspec ./spec/features/work_packages/table/switch_types_spec.rb[1:1:2]`

Treat this as a standalone task, unrelated to PR #24779. Create a new branch from origin/dev and open a new pull request targeting dev — do not stack it on PR #24779 or reuse that branch.

Follow the playbook in docs/development/testing/handling-flaky-tests/README.md to find the root cause and fix the underlying race — do not skip, delete, or weaken the spec to make it pass; disabling is a last resort per the playbook, and only with a bug ticket. Verify the fix by running the spec(s) repeatedly (e.g. `script/bulk_run_rspec --run-count 10`).

If you cannot reproduce the flake or are not confident in a fix after reasonable investigation, do not fabricate a change or skip the spec to force CI green. Instead, leave the pull request in draft and document what you tried, the suspected cause, and any leads in its description, then assign @myabc to take over.

Once the fix is verified, title the PR after the spec(s) it fixes, and use the PR description to explain the root cause, how the change resolves it, and the before/after results. Label the PR `flaky-spec`, assign @myabc, and request a review from @myabc.
On every commit, set @myabc as the sole co-author with a `Co-authored-by:` trailer (use their GitHub no-reply email so it links to their account), so it is traceable who dispatched the fix.

@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 46ec91f to 0adff93 Compare September 1, 2026 20:53
@ulferts
ulferts self-requested a review September 2, 2026 12:23
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 0adff93 to 0154d4b Compare September 4, 2026 10:06
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 0154d4b to 0bf78a8 Compare September 5, 2026 11:30
myabc added 18 commits September 5, 2026 17:24
Adds live action scopes for ordered destination batches while keeping
selection state out of the rendered menu. Intersects candidates across
mobility and list ownership.

https://community.openproject.org/wp/AGILE-362
Projects server candidates from the live action scope. Omits unavailable
and fully occupied destinations while covering deferred, confined, and
fixed-card projection.

https://community.openproject.org/wp/AGILE-362
Adds a cancelable pre-load hook and form-backed POST support. Sends
ordered IDs without encoding batches in cacheable URLs while preserving
request.js CSRF and Turbo Stream handling.

https://community.openproject.org/wp/AGILE-362
Reloads every member and intersects authoritative destinations. Rejects
stale, invisible, foreign, or mixed batches atomically and shares the same
destination policy with under-lock submission checks.

https://community.openproject.org/wp/AGILE-362
Renders dialogs from ordered work-package batches and consumes
controller-filtered destination collections. Keeps form IDs ordered and
removes a redundant project input.

https://community.openproject.org/wp/AGILE-362
Submits direct and dialog moves through the collection endpoint while
preserving ordered IDs and retry selection. Tracks in-flight movement across
disconnect and reconnect to prevent duplicate requests and stale busy state.

https://community.openproject.org/wp/AGILE-362
Renders live-region feedback from the authoritative response while
suppressing misleading positions for invisible results.

https://community.openproject.org/wp/AGILE-362
Exercises ordered destination batches through Selenium, covering
confinement, stale menus, rejection, and selection cleanup. Rejects every
visible modal in the disappearing-target case.

https://community.openproject.org/wp/AGILE-362
Serializes append placement with destination lifecycle changes and locks
target rows before rechecking authority. Batches read-only status queries and
cleans up concurrency tests without masking original failures.

https://community.openproject.org/wp/AGILE-362
Serializes unanchored placement before resolving target state. Orders
source and target lifecycle mutexes before item locks and revalidates each
captured source under lock.

https://community.openproject.org/wp/AGILE-362
Preserves the retry batch while a request owns movement.
Consumes selection Escape only after overlays and empty state are handled.

https://community.openproject.org/wp/AGILE-362
Shares the two matching non-optimistic response paths while keeping
caller-owned cleanup and #AGILE-393 behavior unchanged. Deduplicates owner
identity without merging policy layers.

https://community.openproject.org/wp/AGILE-362
Updates the inherited sprint-completion race to assert the shared unavailable-destination policy used by collection moves.

https://community.openproject.org/wp/AGILE-362
Replaces the persisted-order database checks with a reload and a
rendered-order assertion, so the spec proves persistence by what the
user sees surviving the reload. Payload and ordering semantics remain
covered by the controller unit specs and the request specs.
The move dialogs stated only how many work packages were selected, so a
batch assembled by rubber band or shift-click could not be checked
before committing to the move. They now list every affected work
package, reusing the treatment the work package delete dialog already
gives its descendants.
"Selected items" named neither how many work packages a move would
touch nor what they were. A pluralised heading states both, and
carrying the description id makes the visible text the destination
select's description instead of a duplicate hidden node.
A long selection outgrows the 320px medium dialog and scrolls behind
an unmarked footer, leaving Cancel and Move floating over the list.
The suites pin the platform to Windows, where the selection orchestrator
reads Ctrl alone and treats a Meta-modified click as a plain one, so the
scope and destination examples were collapsing their own batches. Builds
them through Ctrl, as the selection specs do.
@myabc
myabc force-pushed the implementation/AGILE-362-batch-destinations branch from 0bf78a8 to 7632202 Compare September 5, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature javascript Pull requests that update Javascript code needs review ruby Pull requests that update Ruby code

Development

Successfully merging this pull request may close these issues.

2 participants