mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-01 14:59:19 +08:00
fix(connectors): only flag a Bitbucket listing capped when the cap withheld something
Review round 2. takeIndexableWithinCap reports capReached as soon as the running total equals maxItems, which is also true of a listing that ended at exactly that count. Setting listingCapped there suppressed deletion reconciliation for a complete listing, so upstream-deleted files and pull requests could stay in the knowledge base indefinitely. applyMaxItemsCap now takes whether Bitbucket had more content beyond the page -- a next link, or directories still queued on the frontier -- and flags the listing only when the cap actually withheld something, matching the Databricks, Google Chat, and Workday connectors.
This commit is contained in:
@@ -357,6 +357,44 @@ describe('bitbucket maxItems cap', () => {
|
||||
expect(syncContext.listingCapped).toBe(true)
|
||||
})
|
||||
|
||||
it('leaves the listing reconcilable when a complete listing ends exactly on the cap', async () => {
|
||||
mockApi([[/\/src\//, () => jsonResponse({ values: [fileEntry('a.md'), fileEntry('b.md')] })]])
|
||||
|
||||
const syncContext: Record<string, unknown> = {}
|
||||
const result = await bitbucketConnector.listDocuments(
|
||||
ACCESS_TOKEN,
|
||||
{ ...CONFIG, maxItems: '2' },
|
||||
undefined,
|
||||
syncContext
|
||||
)
|
||||
|
||||
expect(result.documents).toHaveLength(2)
|
||||
expect(syncContext.listingCapped).toBeUndefined()
|
||||
})
|
||||
|
||||
it('flags the listing capped when the cap lands on a page boundary with more to come', async () => {
|
||||
mockApi([
|
||||
[
|
||||
/\/src\//,
|
||||
() =>
|
||||
jsonResponse({
|
||||
values: [fileEntry('a.md'), fileEntry('b.md')],
|
||||
next: 'https://api.bitbucket.org/2.0/repositories/acme/widgets/src/abc/?page=2',
|
||||
}),
|
||||
],
|
||||
])
|
||||
|
||||
const syncContext: Record<string, unknown> = {}
|
||||
await bitbucketConnector.listDocuments(
|
||||
ACCESS_TOKEN,
|
||||
{ ...CONFIG, maxItems: '2' },
|
||||
undefined,
|
||||
syncContext
|
||||
)
|
||||
|
||||
expect(syncContext.listingCapped).toBe(true)
|
||||
})
|
||||
|
||||
it('leaves the listing reconcilable when no cap is configured', async () => {
|
||||
mockApi([[/\/src\//, () => jsonResponse({ values: [fileEntry('a.md'), fileEntry('b.md')] })]])
|
||||
|
||||
|
||||
@@ -714,11 +714,20 @@ function pendingDirectories(syncContext: Record<string, unknown> | undefined): s
|
||||
* Applies the optional maxItems cap to a page, tracking the running total in
|
||||
* syncContext and flagging `listingCapped` when the cap truncates the listing.
|
||||
* Skipped (oversized) documents ride along without consuming the cap.
|
||||
*
|
||||
* `sourceHasMore` is whether Bitbucket still had content beyond this page — a
|
||||
* `next` link, or directories still queued on the frontier. It is required because
|
||||
* `takeIndexableWithinCap` reports `capReached` as soon as the running total
|
||||
* *equals* maxItems, which is also true of a listing that ended at exactly that
|
||||
* count. Setting `listingCapped` there would suppress deletion reconciliation for
|
||||
* a listing that was in fact complete, leaving upstream-deleted files indexed
|
||||
* indefinitely, so the flag is set only when the cap actually withheld something.
|
||||
*/
|
||||
function applyMaxItemsCap(
|
||||
documents: ExternalDocument[],
|
||||
maxItems: number,
|
||||
syncContext: Record<string, unknown> | undefined
|
||||
syncContext: Record<string, unknown> | undefined,
|
||||
sourceHasMore: boolean
|
||||
): { documents: ExternalDocument[]; capped: boolean } {
|
||||
if (maxItems <= 0) return { documents, capped: false }
|
||||
const alreadyIndexed = (syncContext?.totalDocsFetched as number) ?? 0
|
||||
@@ -734,7 +743,8 @@ function applyMaxItemsCap(
|
||||
)
|
||||
if (syncContext) {
|
||||
syncContext.totalDocsFetched = alreadyIndexed + indexableCount
|
||||
if (capReached) syncContext.listingCapped = true
|
||||
const withheld = taken.length < documents.length || sourceHasMore
|
||||
if (capReached && withheld) syncContext.listingCapped = true
|
||||
}
|
||||
return { documents: taken, capped: capReached }
|
||||
}
|
||||
@@ -922,7 +932,8 @@ export const bitbucketConnector: ConnectorConfig = {
|
||||
const { documents: capped, capped: hitLimit } = applyMaxItemsCap(
|
||||
documents,
|
||||
maxItems,
|
||||
syncContext
|
||||
syncContext,
|
||||
Boolean(page.next) || frontier.length > 0
|
||||
)
|
||||
if (hitLimit) return { documents: capped, hasMore: false }
|
||||
|
||||
@@ -990,7 +1001,8 @@ export const bitbucketConnector: ConnectorConfig = {
|
||||
const { documents: capped, capped: hitLimit } = applyMaxItemsCap(
|
||||
documents,
|
||||
maxItems,
|
||||
syncContext
|
||||
syncContext,
|
||||
Boolean(page.next)
|
||||
)
|
||||
if (hitLimit) return { documents: capped, hasMore: false }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user