Skip to content

Commit 179d80c

Browse files
committed
fix(search): harden connector recovery and file lifecycle
1 parent 7e5a792 commit 179d80c

14 files changed

Lines changed: 917 additions & 86 deletions

File tree

apps/docs/content/docs/search/github.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ GitHub Search indexes text files from a repository on `github.com`. A workspace
1111

1212
## Before you start
1313

14-
Your Sim deployment needs a GitHub App configured as described below. For private repositories, an owner or administrator must install that App on them. Each person needs a verified GitHub email address matching their Sim account; the address can be private or secondary.
14+
Your Sim deployment needs a GitHub App configured as described below. The repository must contain at least one commit; initialize an empty repository with a README before adding it. For private repositories, an owner or administrator must install that App on them. Each person needs a verified GitHub email address matching their Sim account; the address can be private or secondary.
1515

1616
## Configure the GitHub App
1717

@@ -144,7 +144,8 @@ With **Connected members**, indexing begins after someone connects. A dedicated
144144
| Identity verification fails | Verify the email used by your Sim account in GitHub's email settings, then reconnect. A public profile email alone is insufficient. |
145145
| Authorization fails after installation | Return to Sim and start **Connect account** there. Do not enable authorization during installation. |
146146
| Sync is incomplete | Review the source status. Very large Git trees, file size limits, and unreadable files can limit indexing. |
147+
| Empty repository returns an error | Add an initial commit, then sync again. GitHub does not return a file tree for an uninitialized repository. |
147148

148149
<Callout type="info">
149-
GitHub Search currently covers repository text files. Issues, pull requests, separate wikis, GitHub Enterprise Server, and `ghe.com` domains are not supported by this connector. Personal access tokens remain available for general knowledge-base connectors, with that knowledge base's access rules.
150+
GitHub Search covers repository text files up to 100 MB, including symbolic links to files within the same repository. Path and extension filters apply to the link's path. Broken or external links, binaries, and submodules are not indexed. Issues, pull requests, separate wikis, GitHub Enterprise Server, and `ghe.com` domains are not supported by this connector. Personal access tokens remain available for general knowledge-base connectors, with that knowledge base's access rules.
150151
</Callout>

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -589,9 +589,6 @@ export function KnowledgeBase({
589589
)
590590
}
591591

592-
/**
593-
* Handles retrying a failed document processing
594-
*/
595592
const handleRetryDocument = (docId: string) => {
596593
updateDocument(docId, {
597594
processingStatus: 'pending',
@@ -1255,7 +1252,7 @@ export function KnowledgeBase({
12551252
</span>
12561253
),
12571254
},
1258-
size: { label: formatFileSize(doc.fileSize) },
1255+
size: { label: formatFileSize(doc.fileSize, { includeBytes: true }) },
12591256
tokens: {
12601257
label:
12611258
doc.processingStatus === 'completed'
@@ -1551,6 +1548,13 @@ export function KnowledgeBase({
15511548
? () => handleViewDocumentTags(contextMenuDocument)
15521549
: undefined
15531550
}
1551+
onRetry={
1552+
contextMenuDocument?.processingStatus === 'failed' &&
1553+
selectedDocumentCount === 1 &&
1554+
userPermissions.canEdit
1555+
? () => handleRetryDocument(contextMenuDocument.id)
1556+
: undefined
1557+
}
15541558
onDelete={
15551559
contextMenuDocument
15561560
? selectedDocumentCount > 1
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { DocumentContextMenu } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/document-context-menu/document-context-menu'
8+
9+
class ResizeObserverMock {
10+
observe = vi.fn()
11+
unobserve = vi.fn()
12+
disconnect = vi.fn()
13+
}
14+
15+
let container: HTMLDivElement
16+
let root: Root
17+
18+
beforeEach(() => {
19+
vi.stubGlobal('ResizeObserver', ResizeObserverMock)
20+
container = document.createElement('div')
21+
document.body.appendChild(container)
22+
root = createRoot(container)
23+
})
24+
25+
afterEach(() => {
26+
act(() => root.unmount())
27+
container.remove()
28+
vi.unstubAllGlobals()
29+
})
30+
31+
describe('DocumentContextMenu retry', () => {
32+
it('offers an accessible Retry action and invokes the provided handler on selection', () => {
33+
const onRetry = vi.fn()
34+
const onClose = vi.fn()
35+
36+
act(() => {
37+
root.render(
38+
<DocumentContextMenu
39+
isOpen
40+
position={{ x: 0, y: 0 }}
41+
onClose={onClose}
42+
hasDocument
43+
selectedCount={1}
44+
onRetry={onRetry}
45+
/>
46+
)
47+
})
48+
49+
const item = document.body.querySelector<HTMLElement>('[role="menuitem"]')
50+
expect(item).toHaveAccessibleName('Retry')
51+
expect(item).not.toHaveAttribute('aria-disabled', 'true')
52+
53+
act(() => item?.click())
54+
55+
expect(onRetry).toHaveBeenCalledOnce()
56+
expect(onClose).toHaveBeenCalledOnce()
57+
})
58+
59+
it.each([
60+
{ scenario: 'no retry handler', selectedCount: 1, hasDocument: true, canRetry: false },
61+
{ scenario: 'multiple documents', selectedCount: 2, hasDocument: true, canRetry: true },
62+
{ scenario: 'empty space', selectedCount: 0, hasDocument: false, canRetry: true },
63+
])('does not offer Retry for $scenario', ({ selectedCount, hasDocument, canRetry }) => {
64+
const onRetry = vi.fn()
65+
66+
act(() => {
67+
root.render(
68+
<DocumentContextMenu
69+
isOpen
70+
position={{ x: 0, y: 0 }}
71+
onClose={vi.fn()}
72+
hasDocument={hasDocument}
73+
selectedCount={selectedCount}
74+
onRetry={canRetry ? onRetry : undefined}
75+
/>
76+
)
77+
})
78+
79+
expect(document.body.querySelector('[role="menuitem"]')).toBeNull()
80+
expect(onRetry).not.toHaveBeenCalled()
81+
})
82+
})

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/document-context-menu/document-context-menu.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
DropdownMenuSeparator,
88
DropdownMenuTrigger,
99
} from '@sim/emcn'
10-
import { Eye, Pencil, Plus, SquareArrowUpRight, TagIcon, Trash } from '@sim/emcn/icons'
10+
import { Eye, Pencil, Plus, RefreshCw, SquareArrowUpRight, TagIcon, Trash } from '@sim/emcn/icons'
1111
import {
1212
selectionActionLabel,
1313
selectionToggleActionLabel,
@@ -22,6 +22,7 @@ interface DocumentContextMenuProps {
2222
onRename?: () => void
2323
onToggleEnabled?: () => void
2424
onViewTags?: () => void
25+
onRetry?: () => void
2526
onDelete?: () => void
2627
onAddDocument?: () => void
2728
isDocumentEnabled?: boolean
@@ -50,6 +51,7 @@ export function DocumentContextMenu({
5051
onRename,
5152
onToggleEnabled,
5253
onViewTags,
54+
onRetry,
5355
onDelete,
5456
onAddDocument,
5557
isDocumentEnabled = true,
@@ -74,7 +76,7 @@ export function DocumentContextMenu({
7476

7577
const hasNavigationSection = !isMultiSelect && (!!onOpenInNewTab || !!onOpenSource)
7678
const hasEditSection = !isMultiSelect && (!!onRename || !!onViewTags)
77-
const hasStateSection = !!onToggleEnabled
79+
const hasStateSection = !!onToggleEnabled || (!isMultiSelect && !!onRetry)
7880
const hasDestructiveSection = !!onDelete
7981
const hasActionsAboveDestructive = hasNavigationSection || hasEditSection || hasStateSection
8082

@@ -132,6 +134,12 @@ export function DocumentContextMenu({
132134
{toggleLabel}
133135
</DropdownMenuItem>
134136
)}
137+
{!isMultiSelect && onRetry && (
138+
<DropdownMenuItem onSelect={onRetry}>
139+
<RefreshCw />
140+
Retry
141+
</DropdownMenuItem>
142+
)}
135143

136144
{hasActionsAboveDestructive && hasDestructiveSection && <DropdownMenuSeparator />}
137145
{onDelete && (

0 commit comments

Comments
 (0)