Skip to content

Commit 647e2e2

Browse files
fix(knowledge): page connector documents in editor
1 parent 61d2ec4 commit 647e2e2

3 files changed

Lines changed: 143 additions & 17 deletions

File tree

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,19 +402,27 @@ interface DocumentsTabProps {
402402
function DocumentsTab({ knowledgeBaseId, connectorId }: DocumentsTabProps) {
403403
const [filter, setFilter] = useState<'active' | 'excluded'>('active')
404404

405-
const { data, isLoading } = useConnectorDocuments(knowledgeBaseId, connectorId, {
406-
includeExcluded: true,
407-
})
405+
const { data, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage } = useConnectorDocuments(
406+
knowledgeBaseId,
407+
connectorId,
408+
{
409+
includeExcluded: true,
410+
}
411+
)
408412

409413
const { mutate: excludeDoc, isPending: isExcluding } = useExcludeConnectorDocument()
410414
const { mutate: restoreDoc, isPending: isRestoring } = useRestoreConnectorDocument()
411415

412416
const documents = useMemo(() => {
413-
if (!data?.documents) return []
414-
return data.documents.filter((d) => (filter === 'excluded' ? d.userExcluded : !d.userExcluded))
415-
}, [data?.documents, filter])
417+
const loadedDocuments = data?.pages.flatMap((page) => page.documents) ?? []
418+
return loadedDocuments.filter((document) =>
419+
filter === 'excluded' ? document.userExcluded : !document.userExcluded
420+
)
421+
}, [data?.pages, filter])
416422

417-
const counts = data?.counts ?? { active: 0, excluded: 0 }
423+
const counts = data?.pages[0]?.counts ?? { active: 0, excluded: 0 }
424+
const visibleDocumentCount = filter === 'excluded' ? counts.excluded : counts.active
425+
const hasMoreVisibleDocuments = Boolean(hasNextPage && documents.length < visibleDocumentCount)
418426

419427
if (isLoading) {
420428
return (
@@ -435,7 +443,7 @@ function DocumentsTab({ knowledgeBaseId, connectorId }: DocumentsTabProps) {
435443
</ButtonGroup>
436444

437445
<div className='max-h-[320px] min-h-0 overflow-y-auto [scrollbar-gutter:stable]'>
438-
{documents.length === 0 ? (
446+
{visibleDocumentCount === 0 ? (
439447
<p className='rounded-lg bg-[var(--surface-3)] px-3 py-8 text-center text-[var(--text-muted)] text-small'>
440448
{filter === 'excluded' ? 'No excluded documents' : 'No documents yet'}
441449
</p>
@@ -488,6 +496,17 @@ function DocumentsTab({ knowledgeBaseId, connectorId }: DocumentsTabProps) {
488496
</Button>
489497
</div>
490498
))}
499+
{hasMoreVisibleDocuments && (
500+
<Button
501+
variant='ghost-secondary'
502+
size='sm'
503+
className='w-full'
504+
disabled={isFetchingNextPage}
505+
onClick={() => fetchNextPage()}
506+
>
507+
{isFetchingNextPage ? 'Loading…' : 'Load more documents'}
508+
</Button>
509+
)}
491510
</div>
492511
)}
493512
</div>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
5+
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
7+
const mocks = vi.hoisted(() => ({
8+
requestJson: vi.fn(),
9+
useInfiniteQuery: vi.fn(),
10+
}))
11+
12+
vi.mock('@tanstack/react-query', () => ({
13+
keepPreviousData: Symbol('keepPreviousData'),
14+
useInfiniteQuery: mocks.useInfiniteQuery,
15+
useMutation: vi.fn(),
16+
useQuery: vi.fn(),
17+
useQueryClient: vi.fn(() => ({ invalidateQueries: vi.fn() })),
18+
}))
19+
20+
vi.mock('@/lib/api/client/request', () => ({
21+
requestJson: mocks.requestJson,
22+
}))
23+
24+
import { listKnowledgeConnectorDocumentsContract } from '@/lib/api/contracts/knowledge'
25+
import { MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_PAGE_SIZE } from '@/lib/knowledge/constants'
26+
import { useConnectorDocuments } from '@/hooks/queries/kb/connectors'
27+
28+
interface ConnectorDocumentsPage {
29+
documents: Array<{ id: string }>
30+
counts: { active: number; excluded: number }
31+
}
32+
33+
interface ConnectorDocumentsQueryOptions {
34+
initialPageParam: number
35+
queryFn: (context: { signal: AbortSignal; pageParam: number }) => Promise<unknown>
36+
getNextPageParam: (
37+
lastPage: ConnectorDocumentsPage,
38+
pages: ConnectorDocumentsPage[]
39+
) => number | undefined
40+
}
41+
42+
describe('useConnectorDocuments', () => {
43+
beforeEach(() => {
44+
vi.clearAllMocks()
45+
})
46+
47+
it('requests bounded pages and advances until the authoritative total is loaded', async () => {
48+
const firstPage = {
49+
documents: [{ id: 'document-1' }, { id: 'document-2' }],
50+
counts: { active: 2, excluded: 1 },
51+
}
52+
const finalPage = {
53+
documents: [{ id: 'document-3' }],
54+
counts: firstPage.counts,
55+
}
56+
mocks.requestJson.mockResolvedValue({ data: firstPage })
57+
58+
useConnectorDocuments('knowledge-1', 'connector-1', { includeExcluded: true })
59+
60+
const options = mocks.useInfiniteQuery.mock.calls[0]?.[0] as ConnectorDocumentsQueryOptions
61+
const signal = new AbortController().signal
62+
await options.queryFn({ signal, pageParam: 200 })
63+
64+
expect(mocks.requestJson).toHaveBeenCalledWith(listKnowledgeConnectorDocumentsContract, {
65+
params: { id: 'knowledge-1', connectorId: 'connector-1' },
66+
query: {
67+
includeExcluded: true,
68+
limit: MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_PAGE_SIZE,
69+
offset: 200,
70+
},
71+
signal,
72+
})
73+
expect(options.initialPageParam).toBe(0)
74+
expect(options.getNextPageParam(firstPage, [firstPage])).toBe(2)
75+
expect(options.getNextPageParam(finalPage, [firstPage, finalPage])).toBeUndefined()
76+
})
77+
78+
it('does not page toward excluded documents when they were not requested', () => {
79+
const activePage = {
80+
documents: [{ id: 'document-1' }, { id: 'document-2' }],
81+
counts: { active: 2, excluded: 10 },
82+
}
83+
84+
useConnectorDocuments('knowledge-1', 'connector-1')
85+
86+
const options = mocks.useInfiniteQuery.mock.calls[0]?.[0] as ConnectorDocumentsQueryOptions
87+
expect(options.getNextPageParam(activePage, [activePage])).toBeUndefined()
88+
})
89+
})

apps/sim/hooks/queries/kb/connectors.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { createLogger } from '@sim/logger'
2-
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2+
import {
3+
keepPreviousData,
4+
useInfiniteQuery,
5+
useMutation,
6+
useQuery,
7+
useQueryClient,
8+
} from '@tanstack/react-query'
39
import { requestJson } from '@/lib/api/client/request'
410
import {
511
type ConnectorData,
@@ -15,6 +21,7 @@ import {
1521
triggerKnowledgeConnectorSyncContract,
1622
updateKnowledgeConnectorContract,
1723
} from '@/lib/api/contracts/knowledge'
24+
import { MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_PAGE_SIZE } from '@/lib/knowledge/constants'
1825
import { knowledgeKeys } from '@/hooks/queries/utils/knowledge-keys'
1926

2027
const logger = createLogger('KnowledgeConnectorQueries')
@@ -245,11 +252,16 @@ async function fetchConnectorDocuments(
245252
knowledgeBaseId: string,
246253
connectorId: string,
247254
includeExcluded: boolean,
255+
offset: number,
248256
signal?: AbortSignal
249257
): Promise<ConnectorDocumentsData> {
250258
const result = await requestJson(listKnowledgeConnectorDocumentsContract, {
251259
params: { id: knowledgeBaseId, connectorId },
252-
query: { includeExcluded },
260+
query: {
261+
includeExcluded,
262+
limit: MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_PAGE_SIZE,
263+
offset,
264+
},
253265
signal,
254266
})
255267

@@ -261,18 +273,24 @@ export function useConnectorDocuments(
261273
connectorId?: string,
262274
options?: { includeExcluded?: boolean }
263275
) {
264-
return useQuery({
265-
queryKey: [
266-
...connectorDocumentKeys.list(knowledgeBaseId, connectorId),
267-
options?.includeExcluded ?? false,
268-
],
269-
queryFn: ({ signal }) =>
276+
const includeExcluded = options?.includeExcluded ?? false
277+
return useInfiniteQuery({
278+
queryKey: [...connectorDocumentKeys.list(knowledgeBaseId, connectorId), includeExcluded],
279+
queryFn: ({ signal, pageParam }) =>
270280
fetchConnectorDocuments(
271281
knowledgeBaseId as string,
272282
connectorId as string,
273-
options?.includeExcluded ?? false,
283+
includeExcluded,
284+
pageParam,
274285
signal
275286
),
287+
initialPageParam: 0,
288+
getNextPageParam: (lastPage, pages) => {
289+
const loadedCount = pages.reduce((total, page) => total + page.documents.length, 0)
290+
const totalCount = lastPage.counts.active + (includeExcluded ? lastPage.counts.excluded : 0)
291+
if (lastPage.documents.length === 0 || loadedCount >= totalCount) return undefined
292+
return loadedCount
293+
},
276294
enabled: Boolean(knowledgeBaseId && connectorId),
277295
staleTime: CONNECTOR_DOCUMENT_LIST_STALE_TIME,
278296
placeholderData: keepPreviousData,

0 commit comments

Comments
 (0)