Skip to content

Commit ea8b877

Browse files
fix(knowledge): preserve pending connector syncs
1 parent 531cde5 commit ea8b877

2 files changed

Lines changed: 88 additions & 16 deletions

File tree

apps/sim/lib/knowledge/orchestration/connectors.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,67 @@ describe('performUpdateKnowledgeConnector', () => {
375375
expect(mockDispatchSync).not.toHaveBeenCalled()
376376
})
377377

378+
it('preserves an already-due source sync when scheduled sync is disabled', async () => {
379+
const pendingSourceSyncAt = new Date(0)
380+
dbChainMockFns.limit.mockResolvedValueOnce([
381+
{
382+
id: 'conn-1',
383+
connectorType: 'notion',
384+
nextSyncAt: pendingSourceSyncAt,
385+
status: 'active',
386+
},
387+
])
388+
dbChainMockFns.returning.mockResolvedValueOnce([
389+
{
390+
id: 'conn-1',
391+
connectorType: 'notion',
392+
nextSyncAt: pendingSourceSyncAt,
393+
status: 'active',
394+
},
395+
])
396+
397+
const outcome = await performUpdateKnowledgeConnector({
398+
...ACTOR,
399+
knowledgeBase: KB,
400+
connectorId: 'conn-1',
401+
updates: { syncIntervalMinutes: 0 },
402+
resolveBillingAttribution,
403+
})
404+
405+
expect(outcome).toMatchObject({ success: true })
406+
expect(dbChainMockFns.set).toHaveBeenCalledWith(
407+
expect.objectContaining({ nextSyncAt: pendingSourceSyncAt, syncIntervalMinutes: 0 })
408+
)
409+
expect(mockDispatchSync).not.toHaveBeenCalled()
410+
})
411+
412+
it('rejects an interval update that races with a source-sync due marker', async () => {
413+
dbChainMockFns.limit
414+
.mockResolvedValueOnce([
415+
{ id: 'conn-1', connectorType: 'notion', nextSyncAt: null, status: 'active' },
416+
])
417+
.mockResolvedValueOnce([
418+
{
419+
id: 'conn-1',
420+
connectorType: 'notion',
421+
nextSyncAt: new Date(),
422+
status: 'active',
423+
},
424+
])
425+
dbChainMockFns.returning.mockResolvedValueOnce([])
426+
427+
const outcome = await performUpdateKnowledgeConnector({
428+
...ACTOR,
429+
knowledgeBase: KB,
430+
connectorId: 'conn-1',
431+
updates: { syncIntervalMinutes: 0 },
432+
resolveBillingAttribution,
433+
})
434+
435+
expect(outcome).toMatchObject({ success: false, errorCode: 'conflict' })
436+
expect(mockDispatchSync).not.toHaveBeenCalled()
437+
})
438+
378439
it('rejects a source replacement while synchronization is in progress', async () => {
379440
dbChainMockFns.limit.mockResolvedValueOnce([
380441
{ id: 'conn-1', connectorType: 'notion', status: 'syncing' },

apps/sim/lib/knowledge/orchestration/connectors.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,11 @@ export async function performUpdateKnowledgeConnector(
493493
if (updates.syncIntervalMinutes !== undefined) {
494494
values.syncIntervalMinutes = updates.syncIntervalMinutes
495495
values.nextSyncAt =
496-
updates.syncIntervalMinutes > 0
497-
? new Date(Date.now() + updates.syncIntervalMinutes * 60 * 1000)
498-
: null
496+
existing.nextSyncAt && existing.nextSyncAt <= updateTimestamp
497+
? existing.nextSyncAt
498+
: updates.syncIntervalMinutes > 0
499+
? new Date(updateTimestamp.getTime() + updates.syncIntervalMinutes * 60 * 1000)
500+
: null
499501
}
500502
if (updates.status !== undefined) {
501503
values.status = updates.status
@@ -524,6 +526,13 @@ export async function performUpdateKnowledgeConnector(
524526
if (updates.sourceConfig !== undefined || updates.status !== undefined) {
525527
updateConditions.push(eq(knowledgeConnector.status, existing.status))
526528
}
529+
if (values.nextSyncAt !== undefined) {
530+
updateConditions.push(
531+
existing.nextSyncAt
532+
? eq(knowledgeConnector.nextSyncAt, existing.nextSyncAt)
533+
: isNull(knowledgeConnector.nextSyncAt)
534+
)
535+
}
527536

528537
const [row] = await db
529538
.update(knowledgeConnector)
@@ -532,19 +541,21 @@ export async function performUpdateKnowledgeConnector(
532541
.returning()
533542

534543
if (!row) {
535-
if (updates.sourceConfig !== undefined || updates.status !== undefined) {
536-
const current = await getKnowledgeConnector(kb.id, connectorId)
537-
if (current?.status === 'syncing') {
538-
return fail(
539-
updates.sourceConfig !== undefined
540-
? 'Cannot update source configuration while connector synchronization is in progress'
541-
: 'Cannot change connector status while synchronization is in progress',
542-
'conflict'
543-
)
544-
}
545-
if (current) {
546-
return fail('Connector status changed during the update; retry the request', 'conflict')
547-
}
544+
const current = await getKnowledgeConnector(kb.id, connectorId)
545+
if (current?.status === 'syncing' && updates.sourceConfig !== undefined) {
546+
return fail(
547+
'Cannot update source configuration while connector synchronization is in progress',
548+
'conflict'
549+
)
550+
}
551+
if (current?.status === 'syncing' && updates.status !== undefined) {
552+
return fail(
553+
'Cannot change connector status while synchronization is in progress',
554+
'conflict'
555+
)
556+
}
557+
if (current) {
558+
return fail('Connector changed during the update; retry the request', 'conflict')
548559
}
549560
return fail('Connector not found', 'not_found')
550561
}

0 commit comments

Comments
 (0)