Skip to content

feat(bulk): add bulk random assign functionality#13

Merged
fathiraz merged 9 commits intomainfrom
feat/bulk-random-assign
Mar 29, 2026
Merged

feat(bulk): add bulk random assign functionality#13
fathiraz merged 9 commits intomainfrom
feat/bulk-random-assign

Conversation

@fathiraz
Copy link
Copy Markdown
Owner

@fathiraz fathiraz commented Mar 28, 2026

Summary

Add the ability to randomly assign multiple project items to team members in a single bulk operation.

Features

  • Random Distribution Algorithm: Balanced assignment strategy that distributes items evenly across selected assignees
  • Visual Distribution Preview: Preview the assignment distribution before applying changes
  • Modal UI: Intuitive interface for assignee selection and strategy configuration
  • Background Queue Processing: Sequential processing with rate limiting to prevent GitHub API abuse detection
  • Safe Reassignment: Clears existing assignees before applying new assignments

Technical Changes

Components

  • bulk-actions-bar.tsx: Added "Random Assign" menu item and confirmation handler
  • bulk-random-assign-modal.tsx: Complete modal with distribution preview step

Background Service Worker

  • Added bulkRandomAssign message handler in background/index.ts
  • Implements queue-based sequential processing with proper error handling
  • Rate limiting with 1-second delays between mutation requests

GraphQL

  • Added REMOVE_ASSIGNEES mutation for clearing existing assignees
  • Added GET_ISSUE_ASSIGNEES query to fetch current assignees before reassignment

Testing

  • Select multiple items and assign randomly to team members
  • Verify existing assignees are cleared before new assignment
  • Check rate limiting prevents API abuse (1s delay between requests)
  • Verify UI shows proper progress during bulk operation

Related Commits

This PR includes the cumulative work from:

  • feat(bulk): add bulkRandomAssign message protocol
  • feat(bulk): implement distribution algorithms
  • feat(bulk): create random assign modal shell
  • feat(bulk): add distribution preview step
  • feat(bulk): add random assign menu item

Summary by cubic

Adds bulk “Random Assign” to distribute selected project items across chosen assignees using balanced, random, or round‑robin strategies. Includes a preview and runs in a safe, rate‑limited background job.

  • New Features

    • New “Random Assign (beta)” action in bulk bar (shortcut: Shift+A).
    • Modal to choose assignees and strategy with reshuffle and distribution preview.
    • Clears existing assignees before reassigning; supports multiple assignees per item.
    • Sequential background processing with ~1s delays and progress updates.
    • Adds GET_ISSUE_ASSIGNEES and REMOVE_ASSIGNEES GraphQL ops and a bulkRandomAssign background handler.
  • Bug Fixes

    • Prevent checkbox double toggles; ignore stale async assignee search responses.
    • Always broadcast a terminal queue state when no items resolve; fix active bulk counter handling.
    • Balanced strategy uses random tie‑breaking to avoid bias.
    • Use the selected strategy when sending the background job (no hardcoded “balanced”).

Written for commit 1e9e81a. Summary will update on new commits.

Add the ability to randomly assign multiple project items to team members
in a single bulk operation. This feature includes:

- Random distribution algorithm for balanced assignment
- Visual distribution preview before applying changes
- Modal UI for assignee selection and strategy configuration
- Background queue processing with rate limiting
- GraphQL mutations for clearing and reassigning issue assignees

Files changed:
- bulk-actions-bar.tsx: Add handleConfirmRandomAssign and wire up modal
- bulk-random-assign-modal.tsx: Implement distribution preview and confirmation
- background/index.ts: Add bulkRandomAssign message handler
- graphql/mutations.ts: Add REMOVE_ASSIGNEES mutation
- graphql/queries.ts: Add GET_ISSUE_ASSIGNEES query
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Cursor auto review

Found 1 actionable issue(s) on changed lines.

Adds bulk random assign (modal, distribution helpers, background queue, and GraphQL for clearing assignees). Bug: In the assignee list, the row uses onClick to toggle selection while each Checkbox also toggles on onChange, so a direct checkbox click fires both handlers and the selection does not change. No other high-confidence correctness or security issues stood out in the diff.

Generated automatically when this PR was submitted using Cursor CLI with --model auto.

}}
onClick={() => toggleAssignee(assignee.id)}
>
<Checkbox
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Clicks on the checkbox bubble to the row onClick, so toggleAssignee runs twice (checkbox onChange + row click) and the checked state snaps back. Call stopPropagation on the checkbox click (or drop the duplicate handler / use label+htmlFor only).

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/bulk/bulk-random-assign-modal.tsx">

<violation number="1" location="src/components/bulk/bulk-random-assign-modal.tsx:41">
P2: Async assignee search responses are not guarded against out-of-order completion, so stale results can replace newer query results.</violation>

<violation number="2" location="src/components/bulk/bulk-random-assign-modal.tsx:139">
P1: Clicking the checkbox triggers both its `onChange` and the parent row's `onClick`, so `toggleAssignee` runs twice and the checked state snaps back to its original value. Add `onClick={(e) => e.stopPropagation()}` on the `Checkbox` to prevent the click from bubbling to the row handler.</violation>
</file>

<file name="src/entries/background/index.ts">

<violation number="1" location="src/entries/background/index.ts:1145">
P2: When no items resolve, this returns without sending a final queue update, so the UI can stay stuck in the “Resolving items…” state. Broadcast a terminal queue state before returning.</violation>
</file>

<file name="src/components/bulk/bulk-random-assign-utils.ts">

<violation number="1" location="src/components/bulk/bulk-random-assign-utils.ts:27">
P2: Balanced distribution has deterministic tie-breaking, so extra items always favor earlier assignees instead of being randomly spread.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Fixes identified by cubic-dev-ai code review:

1. Checkbox click handler conflict (P1)
   - Add onClick stopPropagation to prevent double toggle
   - Resolves: bulk-random-assign-modal.tsx:139

2. Async assignee search race condition (P2)
   - Add requestId tracking to ignore stale responses
   - Resolves: bulk-random-assign-modal.tsx:41

3. Missing terminal queue state (P2)
   - Broadcast final state when no items resolve
   - Decrement activeBulkCount before early return
   - Resolves: background/index.ts:1145

4. Deterministic tie-breaking in balanced distribution (P2)
   - Randomly select among candidates with equal minimum counts
   - Prevents extra items from always favoring earlier assignees
   - Resolves: bulk-random-assign-utils.ts:27
@fathiraz
Copy link
Copy Markdown
Owner Author

✅ All Review Issues Resolved

Thanks to @github-actions and @cubic-dev-ai for the thorough review. All 4 identified issues have been fixed:

Issue 1: Checkbox click handler conflict (P1) — RESOLVED

File:
Fix: Added to the Checkbox component to prevent the click event from bubbling to the parent row handler.

Issue 2: Async assignee search race condition (P2) — RESOLVED

File:
Fix: Implemented request ID tracking using . Each search request captures its ID, and responses only update state if the , effectively ignoring stale responses.

Issue 3: Missing terminal queue state (P2) — RESOLVED

File:
Fix: When , now broadcasts a terminal queue state with before returning. Also properly decrements to prevent queue lock.

Issue 4: Deterministic tie-breaking in balanced distribution (P2) — RESOLVED

File:
Fix: Changed the balanced distribution algorithm to collect all candidates with the minimum count, then randomly select among them. Previously, the first assignee in iteration order was always chosen, causing deterministic bias toward earlier assignees.

All changes committed and pushed. Ready for re-review! 🚀

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/bulk/bulk-random-assign-modal.tsx">

<violation number="1" location="src/components/bulk/bulk-random-assign-modal.tsx:45">
P2: The request freshness check is ineffective (`currentRequestId === requestId` is always true), so stale async responses can still update assignees/loading state out of order.</violation>
</file>

<file name="src/entries/background/index.ts">

<violation number="1" location="src/entries/background/index.ts:1147">
P1: `activeBulkCount` is decremented twice on the no-items early return path, which can corrupt bulk concurrency tracking.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Fix 2 issues identified in re-review:

1. Race condition guard ineffective (P2) - bulk-random-assign-modal.tsx:45
   - Problem: currentRequestId === requestId was always true (same closure)
   - Fix: Use useRef to track latestRequestIdRef.current across renders
   - Now properly ignores stale async responses

2. Double decrement of activeBulkCount (P1) - background/index.ts:1147
   - Problem: activeBulkCount-- was called before return AND in finally block
   - Fix: Remove the early decrement; let finally block handle it consistently
   - Prevents corruption of bulk concurrency tracking
@fathiraz
Copy link
Copy Markdown
Owner Author

✅ Re-Review Issues Fixed

Thanks @cubic-dev-ai for the re-review. Both new issues have been addressed:

Issue 1: Race condition guard ineffective (P2) — FIXED

File:
Problem: The comparison was always true because both variables were captured in the same closure.
Fix: Now using useRef to track latestRequestIdRef.current across renders, properly ignoring stale async responses.

Issue 2: Double decrement of activeBulkCount (P1) — FIXED

File:
Problem: activeBulkCount-- was called before the early return AND in the finally block, causing double decrement.
Fix: Removed the early decrement - the finally block handles it consistently for all exit paths.

Changes committed and pushed. Ready for another pass! 🚀

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Mar 29, 2026

✅ Re-Review Issues Fixed

Thanks @cubic-dev-ai for the re-review. Both new issues have been addressed:

Issue 1: Race condition guard ineffective (P2) — FIXED

...

@fathiraz I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/bulk/bulk-actions-bar.tsx">

<violation number="1" location="src/components/bulk/bulk-actions-bar.tsx:402">
P2: The selected random-assign strategy is ignored and the message always sends `balanced`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

…essage

Fixes issue identified by cubic-dev-ai:
- The selected random-assign strategy was being ignored
- Message always sent hardcoded 'balanced' strategy

Changes:
- Update onConfirm callback signature to include strategy parameter
- Pass strategy from modal state when calling onConfirm
- Update handleConfirmRandomAssign to receive and use strategy
- Remove hardcoded 'balanced' value

File: bulk-actions-bar.tsx:402
@fathiraz
Copy link
Copy Markdown
Owner Author

✅ Issue Fixed: Strategy Now Properly Passed

Thanks @cubic-dev-ai for catching this!

Issue: Selected random-assign strategy was ignored, always sent 'balanced'
File:

Fix:

  • Updated callback signature to include
  • Modal now passes state when calling
  • receives strategy and passes it to
  • Removed hardcoded value

The selected strategy (balanced/random/round-robin) is now correctly sent to the background worker.

Changes committed and pushed! 🚀

@fathiraz fathiraz merged commit 3026706 into main Mar 29, 2026
3 checks passed
fathiraz added a commit that referenced this pull request Mar 29, 2026
* main:
  feat(bulk): add bulk random assign functionality (#13)
@fathiraz fathiraz deleted the feat/bulk-random-assign branch March 30, 2026 08:39
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.

1 participant