Skip to content

Commit 5ec3386

Browse files
msujawsclaude
andcommitted
fix: restrict nobody@mozilla.org to backlog column only
Bugzilla requires a real assignee for non-backlog statuses (ASSIGNED, IN_PROGRESS, RESOLVED). Filter out nobody@mozilla.org from the assignee picker in all columns except backlog to prevent invalid status changes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c97d5b4 commit 5ec3386

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed

src/components/Board/Column.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,18 @@ vi.mock('./Card', () => ({
4343
bug,
4444
isSelected,
4545
isGrabbed,
46+
allAssignees,
4647
}: {
4748
bug: BugzillaBug
4849
isSelected?: boolean
4950
isGrabbed?: boolean
51+
allAssignees?: Array<{ email: string; displayName: string }>
5052
}) => (
5153
<div
5254
data-testid={`card-${bug.id.toString()}`}
5355
data-selected={isSelected}
5456
data-grabbed={isGrabbed}
57+
data-assignees={allAssignees ? allAssignees.map((a) => a.email).join(',') : ''}
5558
>
5659
Card: {bug.summary}
5760
</div>
@@ -330,4 +333,60 @@ describe('Column', () => {
330333
expect(card2).toHaveAttribute('data-grabbed', 'true')
331334
})
332335
})
336+
337+
describe('assignee filtering', () => {
338+
const assigneesWithNobody = [
339+
{ email: 'nobody@mozilla.org', displayName: 'Nobody' },
340+
{ email: 'dev@mozilla.com', displayName: 'Developer' },
341+
{ email: 'qa@mozilla.com', displayName: 'QA Engineer' },
342+
]
343+
344+
it('should include nobody@mozilla.org in backlog column', () => {
345+
render(<Column {...defaultProps} column="backlog" allAssignees={assigneesWithNobody} />)
346+
347+
const card = screen.getByTestId('card-12345')
348+
expect(card).toHaveAttribute('data-assignees', expect.stringContaining('nobody@mozilla.org'))
349+
})
350+
351+
it('should exclude nobody@mozilla.org in todo column', () => {
352+
render(<Column {...defaultProps} column="todo" allAssignees={assigneesWithNobody} />)
353+
354+
const card = screen.getByTestId('card-12345')
355+
expect(card).toHaveAttribute(
356+
'data-assignees',
357+
expect.not.stringContaining('nobody@mozilla.org'),
358+
)
359+
expect(card).toHaveAttribute('data-assignees', expect.stringContaining('dev@mozilla.com'))
360+
})
361+
362+
it('should exclude nobody@mozilla.org in in-progress column', () => {
363+
render(<Column {...defaultProps} column="in-progress" allAssignees={assigneesWithNobody} />)
364+
365+
const card = screen.getByTestId('card-12345')
366+
expect(card).toHaveAttribute(
367+
'data-assignees',
368+
expect.not.stringContaining('nobody@mozilla.org'),
369+
)
370+
})
371+
372+
it('should exclude nobody@mozilla.org in in-review column', () => {
373+
render(<Column {...defaultProps} column="in-review" allAssignees={assigneesWithNobody} />)
374+
375+
const card = screen.getByTestId('card-12345')
376+
expect(card).toHaveAttribute(
377+
'data-assignees',
378+
expect.not.stringContaining('nobody@mozilla.org'),
379+
)
380+
})
381+
382+
it('should exclude nobody@mozilla.org in done column', () => {
383+
render(<Column {...defaultProps} column="done" allAssignees={assigneesWithNobody} />)
384+
385+
const card = screen.getByTestId('card-12345')
386+
expect(card).toHaveAttribute(
387+
'data-assignees',
388+
expect.not.stringContaining('nobody@mozilla.org'),
389+
)
390+
})
391+
})
333392
})

src/components/Board/Column.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { useMemo } from 'react'
12
import { useDroppable } from '@dnd-kit/core'
23
import { Card } from './Card'
34
import type { BugzillaBug } from '@/lib/bugzilla/types'
45
import type { KanbanColumn } from '@/lib/bugzilla/status-mapper'
56
import type { Assignee } from '@/hooks/use-board-assignees'
67
import { COLUMN_NAMES } from '@/types'
78

9+
const NOBODY_EMAIL = 'nobody@mozilla.org'
10+
811
interface ColumnProps {
912
column: KanbanColumn
1013
bugs: BugzillaBug[]
@@ -49,6 +52,14 @@ export function Column({
4952
const stagedCount = bugs.filter((bug) => stagedBugIds.has(bug.id)).length
5053
const countId = `${column}-count`
5154

55+
// Filter out nobody@mozilla.org for non-backlog columns
56+
// Bugzilla requires a real assignee for non-backlog statuses (ASSIGNED, IN_PROGRESS, etc.)
57+
const filteredAssignees = useMemo(() => {
58+
if (!allAssignees) return
59+
if (column === 'backlog') return allAssignees
60+
return allAssignees.filter((assignee) => assignee.email !== NOBODY_EMAIL)
61+
}, [allAssignees, column])
62+
5263
// Determine column styling based on state
5364
const getColumnClassName = () => {
5465
const base = 'flex min-h-[500px] w-72 flex-shrink-0 flex-col rounded-lg p-4 transition-colors'
@@ -128,7 +139,7 @@ export function Column({
128139
stagedAssignee={stagedAssignees?.get(bug.id)}
129140
isSelected={selectedIndex === index}
130141
isGrabbed={selectedIndex === index && isGrabbing}
131-
allAssignees={allAssignees}
142+
allAssignees={filteredAssignees}
132143
onAssigneeChange={onAssigneeChange}
133144
/>
134145
))}

src/store/slices/staged-slice.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,24 @@ describe('StagedSlice', () => {
430430
{ id: 123, status: 'ASSIGNED', assigned_to: 'new@example.com' },
431431
])
432432
})
433+
434+
it('should include both status and assignee when assignee changed first then column', async () => {
435+
mockBatchUpdateBugs.mockResolvedValueOnce({
436+
successful: [123],
437+
failed: [],
438+
})
439+
440+
const { stageChange, stageAssigneeChange, applyChanges } = useStore.getState()
441+
442+
// User first changes assignee, then drags to new column
443+
stageAssigneeChange(123, 'old@example.com', 'new@example.com')
444+
stageChange(123, 'backlog', 'todo')
445+
await applyChanges(testApiKey)
446+
447+
expect(mockBatchUpdateBugs).toHaveBeenCalledWith([
448+
{ id: 123, status: 'ASSIGNED', assigned_to: 'new@example.com' },
449+
])
450+
})
433451
})
434452

435453
describe('applyChanges with resolution', () => {

0 commit comments

Comments
 (0)