fix(kanban): include archived tasks in board counts (#619)

This commit is contained in:
Zhicheng Han
2026-05-11 15:09:58 +02:00
committed by GitHub
parent bb639a9121
commit e0e4096605
8 changed files with 32 additions and 14 deletions
+3 -3
View File
@@ -28,12 +28,12 @@ describe('Kanban API', () => {
vi.clearAllMocks()
})
it('serializes board and list filters into query params', async () => {
it('serializes board, list filters, and archived inclusion into query params', async () => {
mockRequest.mockResolvedValue({ tasks: [{ id: 'task-1' }] })
const result = await listTasks({ board: 'default', status: 'blocked', assignee: 'alice', tenant: 'ops' })
const result = await listTasks({ board: 'default', status: 'blocked', assignee: 'alice', tenant: 'ops', includeArchived: true })
expect(mockRequest).toHaveBeenCalledWith('/api/hermes/kanban?board=default&status=blocked&assignee=alice&tenant=ops')
expect(mockRequest).toHaveBeenCalledWith('/api/hermes/kanban?board=default&status=blocked&assignee=alice&tenant=ops&includeArchived=true')
expect(result).toEqual([{ id: 'task-1' }])
})
+2 -2
View File
@@ -63,7 +63,7 @@ describe('Kanban store', () => {
expect(store.loading).toBe(true)
await promise
expect(mockKanbanApi.listTasks).toHaveBeenCalledWith({ board: 'project-a', status: 'blocked', assignee: 'alice' })
expect(mockKanbanApi.listTasks).toHaveBeenCalledWith({ board: 'project-a', status: 'blocked', assignee: 'alice', includeArchived: true })
expect(store.tasks).toEqual([{ id: 'task-1', status: 'todo' }])
expect(store.loading).toBe(false)
})
@@ -128,7 +128,7 @@ describe('Kanban store', () => {
store.setSelectedBoard('project-a')
await store.refreshAll()
expect(mockKanbanApi.listTasks).toHaveBeenCalledWith({ board: 'project-a', status: undefined, assignee: undefined })
expect(mockKanbanApi.listTasks).toHaveBeenCalledWith({ board: 'project-a', status: undefined, assignee: undefined, includeArchived: true })
expect(mockKanbanApi.getStats).toHaveBeenCalledWith({ board: 'project-a' })
expect(mockKanbanApi.getAssignees).toHaveBeenCalledWith({ board: 'project-a' })
expect(mockKanbanApi.listBoards).toHaveBeenCalledWith({ includeArchived: false })
+7 -3
View File
@@ -55,26 +55,30 @@ describe('hermes kanban service', () => {
.mockResolvedValueOnce({ stdout: JSON.stringify([{ id: 'task-1' }]) })
.mockResolvedValueOnce({ stdout: JSON.stringify({ id: 'task-2' }) })
.mockResolvedValueOnce({ stdout: JSON.stringify({ total: 1, by_status: {}, by_assignee: {} }) })
.mockResolvedValueOnce({ stdout: JSON.stringify([{ id: 'archived-1', status: 'archived' }, { id: 'archived-2', status: 'archived' }]) })
await expect(service.listTasks({ board: 'project-a', status: 'todo', assignee: 'alice', tenant: 'ops' })).resolves.toEqual([{ id: 'task-1' }])
await expect(service.listTasks({ board: 'project-a', status: 'todo', assignee: 'alice', tenant: 'ops', includeArchived: true })).resolves.toEqual([{ id: 'task-1' }])
await expect(service.createTask('Ship', { board: 'project-a', body: 'write', assignee: 'alice', priority: 3, tenant: 'ops' })).resolves.toEqual({ id: 'task-2' })
await expect(service.getStats({ board: 'project-a' })).resolves.toEqual({ total: 1, by_status: {}, by_assignee: {} })
await expect(service.getStats({ board: 'project-a' })).resolves.toEqual({ total: 3, by_status: { archived: 2 }, by_assignee: {} })
expect(mockExecFileAsync.mock.calls[0][1]).toEqual(['kanban', '--board', 'project-a', 'list', '--json', '--status', 'todo', '--assignee', 'alice', '--tenant', 'ops'])
expect(mockExecFileAsync.mock.calls[0][1]).toEqual(['kanban', '--board', 'project-a', 'list', '--json', '--archived', '--status', 'todo', '--assignee', 'alice', '--tenant', 'ops'])
expect(mockExecFileAsync.mock.calls[1][1]).toEqual(['kanban', '--board', 'project-a', 'create', 'Ship', '--json', '--body', 'write', '--assignee', 'alice', '--priority', '3', '--tenant', 'ops'])
expect(mockExecFileAsync.mock.calls[2][1]).toEqual(['kanban', '--board', 'project-a', 'stats', '--json'])
expect(mockExecFileAsync.mock.calls[3][1]).toEqual(['kanban', '--board', 'project-a', 'list', '--json', '--archived', '--status', 'archived'])
})
it('normalizes omitted board to default instead of falling through to CLI current', async () => {
mockExecFileAsync
.mockResolvedValueOnce({ stdout: JSON.stringify([]) })
.mockResolvedValueOnce({ stdout: JSON.stringify({ total: 0, by_status: {}, by_assignee: {} }) })
.mockResolvedValueOnce({ stdout: JSON.stringify([]) })
await service.listTasks()
await service.getStats()
expect(mockExecFileAsync.mock.calls[0][1]).toEqual(['kanban', '--board', 'default', 'list', '--json'])
expect(mockExecFileAsync.mock.calls[1][1]).toEqual(['kanban', '--board', 'default', 'stats', '--json'])
expect(mockExecFileAsync.mock.calls[2][1]).toEqual(['kanban', '--board', 'default', 'list', '--json', '--archived', '--status', 'archived'])
})
it('builds action CLI calls and maps not-found show to null', async () => {
+3 -3
View File
@@ -82,9 +82,9 @@ describe('kanban controller', () => {
expect(mockListBoards).toHaveBeenCalledWith({ includeArchived: true })
expect(boardsCtx.body).toEqual({ boards: [{ slug: 'default' }] })
const c = ctx({ query: { board: 'project-a', status: 'todo', assignee: 'alice', tenant: 'ops' } })
const c = ctx({ query: { board: 'project-a', status: 'todo', assignee: 'alice', tenant: 'ops', includeArchived: 'true' } })
await ctrl.list(c)
expect(mockListTasks).toHaveBeenCalledWith({ board: 'project-a', status: 'todo', assignee: 'alice', tenant: 'ops' })
expect(mockListTasks).toHaveBeenCalledWith({ board: 'project-a', status: 'todo', assignee: 'alice', tenant: 'ops', includeArchived: true })
expect(c.body).toEqual({ tasks: [{ id: 'task-1' }] })
mockCreateBoard.mockResolvedValue({ slug: 'project-b' })
@@ -106,7 +106,7 @@ describe('kanban controller', () => {
const defaultCtx = ctx({ query: { status: 'ready' } })
await ctrl.list(defaultCtx)
expect(mockListTasks).toHaveBeenLastCalledWith({ board: 'default', status: 'ready', assignee: undefined, tenant: undefined })
expect(mockListTasks).toHaveBeenLastCalledWith({ board: 'default', status: 'ready', assignee: undefined, tenant: undefined, includeArchived: false })
})
it('enriches completed task details using the latest run profile', async () => {