Skip to content

Commit b0491f1

Browse files
authored
fix(chat): invalidate deployment queries after a chat mutation (#6223)
PATCH /api/chat/manage/[id] calls performFullDeploy when the workflow has drifted from its active deployment, so editing a chat can mint a new deployment version. useUpdateChat invalidated only chatStatus and chatDetail, leaving the deployment panel showing the previous version and a stale "needs redeployment" indicator until the staleTime expired. Both mutations now route through invalidateDeploymentQueries, the shared helper the rest of the deployment surface uses. That also picks up deployedState, which useCreateChat's hand-rolled list had omitted even though performChatDeploy replaces the deployed workflow state. Tests cover both mutations and were verified to fail against the previous invalidation.
1 parent 36aace7 commit b0491f1

2 files changed

Lines changed: 115 additions & 15 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act, type ReactNode } from 'react'
5+
import { sleep } from '@sim/utils/helpers'
6+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
7+
import { createRoot, type Root } from 'react-dom/client'
8+
import { beforeEach, describe, expect, it, vi } from 'vitest'
9+
10+
const { mockRequestJson, mockInvalidateDeploymentQueries } = vi.hoisted(() => ({
11+
mockRequestJson: vi.fn(),
12+
mockInvalidateDeploymentQueries: vi.fn(),
13+
}))
14+
15+
vi.mock('@/lib/api/client/request', () => ({
16+
requestJson: mockRequestJson,
17+
}))
18+
19+
vi.mock('@/hooks/queries/deployments', async (importOriginal) => ({
20+
...(await importOriginal<typeof import('@/hooks/queries/deployments')>()),
21+
invalidateDeploymentQueries: mockInvalidateDeploymentQueries,
22+
}))
23+
24+
import { useCreateChat, useUpdateChat } from '@/hooks/queries/chats'
25+
26+
function renderHookWithClient<T>(useHook: () => T): { getResult: () => T } {
27+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
28+
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
29+
const container = document.createElement('div')
30+
const root: Root = createRoot(container)
31+
let result: T | undefined
32+
33+
function Probe() {
34+
result = useHook()
35+
return null
36+
}
37+
38+
act(() => {
39+
root.render(
40+
<QueryClientProvider client={queryClient}>{(<Probe />) as ReactNode}</QueryClientProvider>
41+
)
42+
})
43+
44+
return {
45+
getResult: () => {
46+
if (result === undefined) throw new Error('Hook result is not ready')
47+
return result
48+
},
49+
}
50+
}
51+
52+
async function flush() {
53+
await act(async () => {
54+
for (let i = 0; i < 5; i++) {
55+
await Promise.resolve()
56+
await sleep(1)
57+
}
58+
})
59+
}
60+
61+
const FORM_DATA = {
62+
identifier: 'my-chat',
63+
title: 'My chat',
64+
description: '',
65+
authType: 'public' as const,
66+
password: '',
67+
emails: [],
68+
welcomeMessage: 'hi',
69+
selectedOutputBlocks: [],
70+
includeThinking: false,
71+
includeToolCalls: false,
72+
}
73+
74+
beforeEach(() => {
75+
vi.clearAllMocks()
76+
mockRequestJson.mockResolvedValue({ chatUrl: 'https://sim.ai/chat/my-chat', chatId: 'chat-1' })
77+
mockInvalidateDeploymentQueries.mockResolvedValue(undefined)
78+
})
79+
80+
describe('chat mutations invalidate the deployment boundary', () => {
81+
/**
82+
* PATCH /api/chat/manage/[id] calls performFullDeploy when the workflow has
83+
* drifted, so a chat edit can mint a new deployment version. Invalidating only
84+
* chatStatus/chatDetail left the deployment panel showing the previous version.
85+
*/
86+
it('useUpdateChat invalidates every deployment query for the workflow', async () => {
87+
const { getResult } = renderHookWithClient(() => useUpdateChat())
88+
89+
await act(async () => {
90+
await getResult().mutateAsync({
91+
chatId: 'chat-1',
92+
workflowId: 'wf-1',
93+
formData: FORM_DATA,
94+
})
95+
})
96+
await flush()
97+
98+
expect(mockInvalidateDeploymentQueries).toHaveBeenCalledWith(expect.anything(), 'wf-1')
99+
})
100+
101+
it('useCreateChat invalidates every deployment query for the workflow', async () => {
102+
const { getResult } = renderHookWithClient(() => useCreateChat())
103+
104+
await act(async () => {
105+
await getResult().mutateAsync({ workflowId: 'wf-1', formData: FORM_DATA })
106+
})
107+
await flush()
108+
109+
expect(mockInvalidateDeploymentQueries).toHaveBeenCalledWith(expect.anything(), 'wf-1')
110+
})
111+
})

apps/sim/hooks/queries/chats.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
verifyChatEmailOtpContract,
2121
} from '@/lib/api/contracts/chats'
2222
import type { OutputConfig } from '@/stores/chat/types'
23-
import { deploymentKeys } from './deployments'
23+
import { deploymentKeys, invalidateDeploymentQueries } from './deployments'
2424

2525
const logger = createLogger('ChatMutations')
2626

@@ -296,17 +296,8 @@ export function useCreateChat() {
296296
throwUserFriendlyIdentifierError(error)
297297
}
298298
},
299-
onSettled: (_data, _error, variables) => {
300-
queryClient.invalidateQueries({
301-
queryKey: deploymentKeys.chatStatus(variables.workflowId),
302-
})
303-
queryClient.invalidateQueries({
304-
queryKey: deploymentKeys.info(variables.workflowId),
305-
})
306-
queryClient.invalidateQueries({
307-
queryKey: deploymentKeys.versions(variables.workflowId),
308-
})
309-
},
299+
onSettled: (_data, _error, variables) =>
300+
invalidateDeploymentQueries(queryClient, variables.workflowId),
310301
onError: (error) => {
311302
logger.error('Failed to create chat', { error })
312303
},
@@ -341,12 +332,10 @@ export function useUpdateChat() {
341332
}
342333
},
343334
onSettled: (_data, _error, variables) => {
344-
queryClient.invalidateQueries({
345-
queryKey: deploymentKeys.chatStatus(variables.workflowId),
346-
})
347335
queryClient.invalidateQueries({
348336
queryKey: deploymentKeys.chatDetail(variables.chatId),
349337
})
338+
return invalidateDeploymentQueries(queryClient, variables.workflowId)
350339
},
351340
onError: (error) => {
352341
logger.error('Failed to update chat', { error })

0 commit comments

Comments
 (0)