Skip to content

Commit 7d4dd26

Browse files
authored
fix(knowledge): fix document processing stuck in processing state (#3857)
* fix(knowledge): fix document processing stuck in processing state * fix(knowledge): use Promise.allSettled for document dispatch and fix Copilot OAuth context - Change Promise.all to Promise.allSettled in processDocumentsWithQueue so one failed dispatch doesn't abort the entire batch - Add writeOAuthReturnContext before showing LazyOAuthRequiredModal from Copilot tools so useOAuthReturnForWorkflow can handle the return - Add consumeOAuthReturnContext on modal close to clean up stale context * fix(knowledge): fix type error in useCredentialRefreshTriggers call Pass empty string instead of undefined for connectorProviderId fallback to match the hook's string parameter type. * upgrade turbo * fix(knowledge): fix type error in connectors-section useCredentialRefreshTriggers call Same string narrowing fix as add-connector-modal — pass empty string fallback for providerId.
1 parent 0abeac7 commit 7d4dd26

File tree

13 files changed

+240
-151
lines changed

13 files changed

+240
-151
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,11 @@ export function KnowledgeBase({
900900
onClick={() => setShowConnectorsModal(true)}
901901
className='flex shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption shadow-[inset_0_0_0_1px_var(--border)] transition-colors hover-hover:bg-[var(--surface-3)]'
902902
>
903-
{ConnectorIcon && <ConnectorIcon className='h-[14px] w-[14px]' />}
903+
{connector.status === 'syncing' ? (
904+
<Loader2 className='h-[14px] w-[14px] animate-spin' />
905+
) : (
906+
ConnectorIcon && <ConnectorIcon className='h-[14px] w-[14px]' />
907+
)}
904908
{def?.name || connector.connectorType}
905909
</button>
906910
)

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

Lines changed: 66 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,17 @@ import {
1919
ModalHeader,
2020
Tooltip,
2121
} from '@/components/emcn'
22-
import { useSession } from '@/lib/auth/auth-client'
23-
import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state'
24-
import {
25-
getCanonicalScopesForProvider,
26-
getProviderIdFromServiceId,
27-
type OAuthProvider,
28-
} from '@/lib/oauth'
22+
import { consumeOAuthReturnContext } from '@/lib/credentials/client-state'
23+
import { getProviderIdFromServiceId, type OAuthProvider } from '@/lib/oauth'
2924
import { ConnectorSelectorField } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field'
30-
import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal'
25+
import { ConnectCredentialModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal'
3126
import { getDependsOnFields } from '@/blocks/utils'
3227
import { CONNECTOR_REGISTRY } from '@/connectors/registry'
3328
import type { ConnectorConfig, ConnectorConfigField } from '@/connectors/types'
3429
import { useCreateConnector } from '@/hooks/queries/kb/connectors'
3530
import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
3631
import type { SelectorKey } from '@/hooks/selectors/types'
32+
import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
3733

3834
const SYNC_INTERVALS = [
3935
{ label: 'Every hour', value: 60 },
@@ -69,7 +65,6 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
6965
const [searchTerm, setSearchTerm] = useState('')
7066

7167
const { workspaceId } = useParams<{ workspaceId: string }>()
72-
const { data: session } = useSession()
7368
const { mutate: createConnector, isPending: isCreating } = useCreateConnector()
7469

7570
const connectorConfig = selectedType ? CONNECTOR_REGISTRY[selectedType] : null
@@ -82,10 +77,16 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
8277
[connectorConfig]
8378
)
8479

85-
const { data: credentials = [], isLoading: credentialsLoading } = useOAuthCredentials(
86-
connectorProviderId ?? undefined,
87-
{ enabled: Boolean(connectorConfig) && !isApiKeyMode, workspaceId }
88-
)
80+
const {
81+
data: credentials = [],
82+
isLoading: credentialsLoading,
83+
refetch: refetchCredentials,
84+
} = useOAuthCredentials(connectorProviderId ?? undefined, {
85+
enabled: Boolean(connectorConfig) && !isApiKeyMode,
86+
workspaceId,
87+
})
88+
89+
useCredentialRefreshTriggers(refetchCredentials, connectorProviderId ?? '', workspaceId)
8990

9091
const effectiveCredentialId =
9192
selectedCredentialId ?? (credentials.length === 1 ? credentials[0].id : null)
@@ -263,51 +264,9 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
263264
)
264265
}
265266

266-
const handleConnectNewAccount = useCallback(async () => {
267-
if (!connectorConfig || !connectorProviderId || !workspaceId) return
268-
269-
const userName = session?.user?.name
270-
const integrationName = connectorConfig.name
271-
const displayName = userName ? `${userName}'s ${integrationName}` : integrationName
272-
273-
try {
274-
const res = await fetch('/api/credentials/draft', {
275-
method: 'POST',
276-
headers: { 'Content-Type': 'application/json' },
277-
body: JSON.stringify({
278-
workspaceId,
279-
providerId: connectorProviderId,
280-
displayName,
281-
}),
282-
})
283-
if (!res.ok) {
284-
setError('Failed to prepare credential. Please try again.')
285-
return
286-
}
287-
} catch {
288-
setError('Failed to prepare credential. Please try again.')
289-
return
290-
}
291-
292-
writeOAuthReturnContext({
293-
origin: 'kb-connectors',
294-
knowledgeBaseId,
295-
displayName,
296-
providerId: connectorProviderId,
297-
preCount: credentials.length,
298-
workspaceId,
299-
requestedAt: Date.now(),
300-
})
301-
267+
const handleConnectNewAccount = useCallback(() => {
302268
setShowOAuthModal(true)
303-
}, [
304-
connectorConfig,
305-
connectorProviderId,
306-
workspaceId,
307-
session?.user?.name,
308-
knowledgeBaseId,
309-
credentials.length,
310-
])
269+
}, [])
311270

312271
const filteredEntries = useMemo(() => {
313272
const term = searchTerm.toLowerCase().trim()
@@ -396,40 +355,40 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
396355
) : (
397356
<div className='flex flex-col gap-2'>
398357
<Label>Account</Label>
399-
{credentialsLoading ? (
400-
<div className='flex items-center gap-2 text-[var(--text-muted)] text-small'>
401-
<Loader2 className='h-4 w-4 animate-spin' />
402-
Loading credentials...
403-
</div>
404-
) : (
405-
<Combobox
406-
size='sm'
407-
options={[
408-
...credentials.map(
409-
(cred): ComboboxOption => ({
410-
label: cred.name || cred.provider,
411-
value: cred.id,
412-
icon: connectorConfig.icon,
413-
})
414-
),
415-
{
416-
label: 'Connect new account',
417-
value: '__connect_new__',
418-
icon: Plus,
419-
onSelect: () => {
420-
void handleConnectNewAccount()
421-
},
358+
<Combobox
359+
size='sm'
360+
options={[
361+
...credentials.map(
362+
(cred): ComboboxOption => ({
363+
label: cred.name || cred.provider,
364+
value: cred.id,
365+
icon: connectorConfig.icon,
366+
})
367+
),
368+
{
369+
label:
370+
credentials.length > 0
371+
? `Connect another ${connectorConfig.name} account`
372+
: `Connect ${connectorConfig.name} account`,
373+
value: '__connect_new__',
374+
icon: Plus,
375+
onSelect: () => {
376+
void handleConnectNewAccount()
422377
},
423-
]}
424-
value={effectiveCredentialId ?? undefined}
425-
onChange={(value) => setSelectedCredentialId(value)}
426-
placeholder={
427-
credentials.length === 0
428-
? `No ${connectorConfig.name} accounts`
429-
: 'Select account'
430-
}
431-
/>
432-
)}
378+
},
379+
]}
380+
value={effectiveCredentialId ?? undefined}
381+
onChange={(value) => setSelectedCredentialId(value)}
382+
onOpenChange={(isOpen) => {
383+
if (isOpen) void refetchCredentials()
384+
}}
385+
placeholder={
386+
credentials.length === 0
387+
? `No ${connectorConfig.name} accounts`
388+
: 'Select account'
389+
}
390+
isLoading={credentialsLoading}
391+
/>
433392
</div>
434393
)}
435394

@@ -590,20 +549,23 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
590549
)}
591550
</ModalContent>
592551
</Modal>
593-
{connectorConfig && connectorConfig.auth.mode === 'oauth' && connectorProviderId && (
594-
<OAuthRequiredModal
595-
isOpen={showOAuthModal}
596-
onClose={() => {
597-
consumeOAuthReturnContext()
598-
setShowOAuthModal(false)
599-
}}
600-
provider={connectorProviderId}
601-
toolName={connectorConfig.name}
602-
requiredScopes={getCanonicalScopesForProvider(connectorProviderId)}
603-
newScopes={[]}
604-
serviceId={connectorConfig.auth.provider}
605-
/>
606-
)}
552+
{showOAuthModal &&
553+
connectorConfig &&
554+
connectorConfig.auth.mode === 'oauth' &&
555+
connectorProviderId && (
556+
<ConnectCredentialModal
557+
isOpen={showOAuthModal}
558+
onClose={() => {
559+
consumeOAuthReturnContext()
560+
setShowOAuthModal(false)
561+
}}
562+
provider={connectorProviderId}
563+
serviceId={connectorConfig.auth.provider}
564+
workspaceId={workspaceId}
565+
knowledgeBaseId={knowledgeBaseId}
566+
credentialCount={credentials.length}
567+
/>
568+
)}
607569
</>
608570
)
609571
}

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
} from '@/lib/oauth'
3737
import { getMissingRequiredScopes } from '@/lib/oauth/utils'
3838
import { EditConnectorModal } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal'
39+
import { ConnectCredentialModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal'
3940
import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal'
4041
import { CONNECTOR_REGISTRY } from '@/connectors/registry'
4142
import type { ConnectorData, SyncLogData } from '@/hooks/queries/kb/connectors'
@@ -46,6 +47,7 @@ import {
4647
useUpdateConnector,
4748
} from '@/hooks/queries/kb/connectors'
4849
import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
50+
import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
4951

5052
const logger = createLogger('ConnectorsSection')
5153

@@ -328,11 +330,16 @@ function ConnectorCard({
328330
const requiredScopes =
329331
connectorDef?.auth.mode === 'oauth' ? (connectorDef.auth.requiredScopes ?? []) : []
330332

331-
const { data: credentials } = useOAuthCredentials(providerId, { workspaceId })
333+
const { data: credentials, refetch: refetchCredentials } = useOAuthCredentials(providerId, {
334+
workspaceId,
335+
})
336+
337+
useCredentialRefreshTriggers(refetchCredentials, providerId ?? '', workspaceId)
332338

333339
const missingScopes = useMemo(() => {
334340
if (!credentials || !connector.credentialId) return []
335341
const credential = credentials.find((c) => c.id === connector.credentialId)
342+
if (!credential) return []
336343
return getMissingRequiredScopes(credential, requiredScopes)
337344
}, [credentials, connector.credentialId, requiredScopes])
338345

@@ -484,15 +491,17 @@ function ConnectorCard({
484491
<Button
485492
variant='active'
486493
onClick={() => {
487-
writeOAuthReturnContext({
488-
origin: 'kb-connectors',
489-
knowledgeBaseId,
490-
displayName: connectorDef?.name ?? connector.connectorType,
491-
providerId: providerId!,
492-
preCount: credentials?.length ?? 0,
493-
workspaceId,
494-
requestedAt: Date.now(),
495-
})
494+
if (connector.credentialId) {
495+
writeOAuthReturnContext({
496+
origin: 'kb-connectors',
497+
knowledgeBaseId,
498+
displayName: connectorDef?.name ?? connector.connectorType,
499+
providerId: providerId!,
500+
preCount: credentials?.length ?? 0,
501+
workspaceId,
502+
requestedAt: Date.now(),
503+
})
504+
}
496505
setShowOAuthModal(true)
497506
}}
498507
className='w-full px-2 py-1 font-medium text-caption'
@@ -510,7 +519,22 @@ function ConnectorCard({
510519
</div>
511520
)}
512521

513-
{showOAuthModal && serviceId && providerId && (
522+
{showOAuthModal && serviceId && providerId && !connector.credentialId && (
523+
<ConnectCredentialModal
524+
isOpen={showOAuthModal}
525+
onClose={() => {
526+
consumeOAuthReturnContext()
527+
setShowOAuthModal(false)
528+
}}
529+
provider={providerId as OAuthProvider}
530+
serviceId={serviceId}
531+
workspaceId={workspaceId}
532+
knowledgeBaseId={knowledgeBaseId}
533+
credentialCount={credentials?.length ?? 0}
534+
/>
535+
)}
536+
537+
{showOAuthModal && serviceId && providerId && connector.credentialId && (
514538
<OAuthRequiredModal
515539
isOpen={showOAuthModal}
516540
onClose={() => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ModalHeader,
1515
} from '@/components/emcn'
1616
import { client } from '@/lib/auth/auth-client'
17+
import type { OAuthReturnContext } from '@/lib/credentials/client-state'
1718
import { writeOAuthReturnContext } from '@/lib/credentials/client-state'
1819
import {
1920
getCanonicalScopesForProvider,
@@ -27,24 +28,30 @@ import { useCreateCredentialDraft } from '@/hooks/queries/credentials'
2728

2829
const logger = createLogger('ConnectCredentialModal')
2930

30-
export interface ConnectCredentialModalProps {
31+
interface ConnectCredentialModalBaseProps {
3132
isOpen: boolean
3233
onClose: () => void
3334
provider: OAuthProvider
3435
serviceId: string
3536
workspaceId: string
36-
workflowId: string
3737
/** Number of existing credentials for this provider — used to detect a successful new connection. */
3838
credentialCount: number
3939
}
4040

41+
export type ConnectCredentialModalProps = ConnectCredentialModalBaseProps &
42+
(
43+
| { workflowId: string; knowledgeBaseId?: never }
44+
| { workflowId?: never; knowledgeBaseId: string }
45+
)
46+
4147
export function ConnectCredentialModal({
4248
isOpen,
4349
onClose,
4450
provider,
4551
serviceId,
4652
workspaceId,
4753
workflowId,
54+
knowledgeBaseId,
4855
credentialCount,
4956
}: ConnectCredentialModalProps) {
5057
const [displayName, setDisplayName] = useState('')
@@ -97,15 +104,19 @@ export function ConnectCredentialModal({
97104
try {
98105
await createDraft.mutateAsync({ workspaceId, providerId, displayName: trimmedName })
99106

100-
writeOAuthReturnContext({
101-
origin: 'workflow',
102-
workflowId,
107+
const baseContext = {
103108
displayName: trimmedName,
104109
providerId,
105110
preCount: credentialCount,
106111
workspaceId,
107112
requestedAt: Date.now(),
108-
})
113+
}
114+
115+
const returnContext: OAuthReturnContext = knowledgeBaseId
116+
? { ...baseContext, origin: 'kb-connectors' as const, knowledgeBaseId }
117+
: { ...baseContext, origin: 'workflow' as const, workflowId: workflowId! }
118+
119+
writeOAuthReturnContext(returnContext)
109120

110121
if (providerId === 'trello') {
111122
window.location.href = '/api/auth/trello/authorize'

0 commit comments

Comments
 (0)