mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(chats): archive chat tree with single query instead of loop (#22496)
## Problem When archiving an agent with subagents, the children briefly flash in the sidebar as root-level items before disappearing. Two issues: 1. **Backend:** Archive used N+1 queries — a recursive DFS (`archiveChatTree`, no transaction) or BFS loop (`chatd.ArchiveChat`, N+1 queries in a tx) to walk the tree and archive each chat individually. 2. **Frontend:** The SSE `deleted` event handler only filtered out the parent chat from the cache. Children remained briefly, got promoted to root-level by `buildChatTree`, then disappeared on the next re-fetch. ## Fix **Backend:** Replace both tree-walk implementations with a single SQL query: ```sql UPDATE chats SET archived = true, updated_at = NOW() WHERE id = @id OR root_chat_id = @id; ``` This leverages the existing `root_chat_id` column (already indexed) to archive the entire tree atomically. **Frontend:** When a `deleted` event arrives, also filter out any chats whose `root_chat_id` matches the deleted chat, so children vanish from the sidebar immediately with the parent. ## Changes - `coderd/database/queries/chats.sql` — Added `ArchiveChatTreeByID` query - `coderd/chats.go` — Use single query, delete `archiveChatTree` function - `coderd/chatd/chatd.go` — Simplify `ArchiveChat` to use single query - `coderd/database/dbauthz/dbauthz.go` — Auth wrapper for new query - `coderd/chats_test.go` — Added `TestArchiveChat/ArchivesChildren` subtest - `site/src/pages/AgentsPage/AgentsPage.tsx` — Filter children in SSE handler - Generated files updated via `make gen`
This commit is contained in:
+2
-32
@@ -517,38 +517,8 @@ func (p *Server) ArchiveChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
return xerrors.Errorf("get chat: %w", err)
|
||||
}
|
||||
|
||||
err = p.db.InTx(func(tx database.Store) error {
|
||||
// Collect descendants breadth-first, then archive from leaves upward.
|
||||
descendantIDs := make([]uuid.UUID, 0)
|
||||
queue := []uuid.UUID{chatID}
|
||||
for len(queue) > 0 {
|
||||
parentID := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
children, err := tx.ListChildChatsByParentID(ctx, parentID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("list children of chat %s: %w", parentID, err)
|
||||
}
|
||||
for _, child := range children {
|
||||
descendantIDs = append(descendantIDs, child.ID)
|
||||
queue = append(queue, child.ID)
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(descendantIDs) - 1; i >= 0; i-- {
|
||||
if err := tx.ArchiveChatByID(ctx, descendantIDs[i]); err != nil {
|
||||
return xerrors.Errorf("archive descendant chat %s: %w", descendantIDs[i], err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.ArchiveChatByID(ctx, chatID); err != nil {
|
||||
return xerrors.Errorf("archive chat: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := p.db.ArchiveChatByID(ctx, chatID); err != nil {
|
||||
return xerrors.Errorf("archive chat: %w", err)
|
||||
}
|
||||
|
||||
p.publishChatPubsubEvent(chat, coderdpubsub.ChatEventKindDeleted)
|
||||
|
||||
Reference in New Issue
Block a user