Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/sim/lib/knowledge/application/search-source-overview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ vi.mock('@/lib/knowledge/read-access', () => ({
}))

import { readSearchSourceOverview } from '@/lib/knowledge/application/search-source-overview'
import { MAX_SEARCH_SOURCE_PROVIDER_TYPES } from '@/lib/knowledge/constants'

const principal = { kind: 'session', userId: 'reader', sessionId: 'session' } as const
const input = { organizationId: 'org-1', workspaceId: null }
Expand All @@ -36,6 +37,14 @@ const searchableProbeCount = () =>
dbChainMockFns.limit.mock.calls.filter(([rows]) => rows === 1).length -
AUTHORIZATION_SINGLE_ROW_READS

/** The configured-provider list is read once, before the batches, under the same bound. */
const CONFIGURED_PROVIDER_READS = 1

/** Every other provider-bounded read in this use case is the indexing probe. */
const indexingProbeCount = () =>
dbChainMockFns.limit.mock.calls.filter(([rows]) => rows === MAX_SEARCH_SOURCE_PROVIDER_TYPES)
.length - CONFIGURED_PROVIDER_READS

function yieldBatches(count: number) {
mocks.batches.mockImplementation(async function* () {
for (let index = 0; index < count; index += 1) yield sql`batch-${sql.raw(String(index))}`
Expand Down Expand Up @@ -73,4 +82,31 @@ describe('readSearchSourceOverview', () => {
expect(result.hasSearchableDocuments).toBe(false)
expect(searchableProbeCount()).toBe(3)
})

it('stops probing for indexing once every configured provider type is known', async () => {
yieldBatches(3)
queueTableRows(member, [{ role: 'owner' }])
queueTableRows(knowledgeConnector, [{ connectorType: 'gmail' }])
queueTableRows(knowledgeConnector, [{ connectorType: 'gmail' }])

const result = await readSearchSourceOverview.execute({ principal, input })

expect(result.providers).toEqual([{ connectorType: 'gmail', isSyncing: true }])
expect(indexingProbeCount()).toBe(1)
})

it('keeps probing every batch while a configured provider type is still unaccounted for', async () => {
yieldBatches(3)
queueTableRows(member, [{ role: 'owner' }])
queueTableRows(knowledgeConnector, [{ connectorType: 'gmail' }, { connectorType: 'notion' }])
queueTableRows(knowledgeConnector, [{ connectorType: 'gmail' }])

const result = await readSearchSourceOverview.execute({ principal, input })

expect(result.providers).toEqual([
{ connectorType: 'gmail', isSyncing: true },
{ connectorType: 'notion', isSyncing: false },
])
expect(indexingProbeCount()).toBe(3)
})
})
16 changes: 15 additions & 1 deletion apps/sim/lib/knowledge/application/search-source-overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,30 @@ export const readSearchSourceOverview = instrumentSourceOverviewUseCase(
/** One searchable document is the whole answer, so later batches skip the probe entirely. */
const probesSearchable: boolean = probesSources && !hasSearchableDocuments
if (probesSearchable) searchableProbes += 1
/**
* A provider type is only read back as membership of `indexingTypes`, so once every
* configured type is in the set no later batch can change the answer.
*/
const probesIndexing: boolean =
probesSources && providers.some(({ connectorType }) => !indexingTypes.has(connectorType))
/** Annotated so the searchable probe's guard does not infer through its own result. */
const [indexing, searchable]: [{ connectorType: string }[], { id: string }[]] =
await Promise.all([
probesSources
probesIndexing
? measureSearchStage('source_overview.indexing', () =>
configuredProvidersQuery()
.where(
and(
configured,
syncingEnabled,
/**
* The probe narrows the configured set the provider list came from, so a
* type already found stays found; excluding it only drops repeated work.
* An empty set adds no predicate rather than a no-op one.
*/
indexingTypes.size > 0
? notInArray(knowledgeConnector.connectorType, [...indexingTypes])
: undefined,
or(
inArray(knowledgeConnector.status, ['pending', 'syncing']),
and(
Expand Down
Loading