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
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/confluence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Central sources combine space permissions, page and ancestor restrictions, and g

Open **Settings → Sources → Confluence**, then a source's **Documents**, **Settings**, or **Sync history**. Invite teammates through **Settings → Members → Invite** or SSO, then have them connect through **Integrations**. **People → Request connections** only requests a provider connection; it does not invite people to the organization.

Syncing runs automatically. Admins can use **Sync now** for an immediate update, **Pause syncing** to stop scheduled syncs, or **Resume syncing** to restart them. **Full resync**, available for service-account connections, fetches unchanged content again and asks for confirmation. Successful manual syncs have a one-minute cooldown; failed syncs can be retried immediately.
Syncing runs automatically. Admins can use **Sync now** for an immediate update, **Pause syncing** to stop scheduled syncs, or **Resume syncing** to restart them. Successful manual syncs have a one-minute cooldown; failed syncs can be retried immediately.

## Troubleshooting

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/connect-your-account.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ On the main **Integrations** page, select **Reconnect** beside the integration i

Admins manage setup from **Settings → Sources**. Open an integration, then its connection to see **Documents**, **Settings**, and **Sync history**. **People** shows account contributors across integrations and supports filtering by integration. This does not grant the admin access to every document.

Syncing runs automatically. Admins can use **Sync now** when they need an update; another manual run is available 60 seconds after a successful sync finishes. Failed or partial runs can be retried immediately. The connection header also offers **Pause syncing** or **Resume syncing**, and **Remove connection**. Where supported, **Full resync** fetches and reindexes all content and asks for confirmation first.
Syncing runs automatically. Admins can use **Sync now** when they need an update; another manual run is available 60 seconds after a successful sync finishes. Failed or partial runs can be retried immediately. The connection header also offers **Pause syncing** or **Resume syncing**, and **Remove connection**.

Removing a Search connection also removes its indexed documents from Sim. The originals remain in the connected app.

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/gitlab.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Open a project to use these administrator actions:
| **Settings** | Change the token, project, filters, or CSV permissions. |
| **Remove connection** | Confirm removal of the connection and its indexed documents. Documents cannot be retained without the connection that maintains their permissions. |

GitLab does not expose a separate **Full resync** action. Each sync checks the selected content. CSV grants change only when you replace the files.
Each sync checks the selected content. CSV grants change only when you replace the files.

## Troubleshooting

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Edit **Settings** to change an existing connection's filters. Adding another con

**Sync using** shows the method selected when the source was created. Add a new connection to change that method. To replace a supported indexing credential, select its replacement and use **Change service account** or **Change account**, as shown.

The connection header offers **Sync now**, **Pause syncing** or **Resume syncing**, **Remove connection**, and, where supported, **Full resync**. Full resync fetches all content again and requires confirmation. Manual runs have a 60-second cooldown after a successful sync finishes; failed or partial runs can be retried immediately. **Pause syncing** becomes available when the current sync finishes.
The connection header offers **Sync now**, **Pause syncing** or **Resume syncing**, and **Remove connection**. Manual runs have a 60-second cooldown after a successful sync finishes; failed or partial runs can be retried immediately. **Pause syncing** becomes available when the current sync finishes.

To deactivate an entire integration, open it from **Settings → Sources**, select **Deactivate**, and confirm. Its content becomes unavailable in Search, Assistant, and MCP; saved connections remain. Select **Activate** on that integration to enable it again.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ describe('ConnectOAuthModal reauthorization', () => {
},
})

expect(container.querySelector('header')).toHaveTextContent('Reconnect Slack')
expect(container).not.toHaveTextContent('tool requires access')
await clickConnect()

expect(mocks.writeOAuthReturnContext).toHaveBeenCalledExactlyOnceWith(
Expand Down Expand Up @@ -424,6 +426,8 @@ describe('ConnectOAuthModal reauthorization', () => {
},
})

expect(container.querySelector('header')).toHaveTextContent('Connect Slack')
expect(container).toHaveTextContent('tool requires access')
await clickConnect()

expect(mocks.writeOAuthReturnContext).toHaveBeenCalledOnce()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,16 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
? `An integration named "${existingCredential.displayName}" already exists.`
: undefined)

const title = `Connect ${providerName}`
const isConnectorReconnect = !isConnect && props.returnContext?.origin === 'kb-connectors'
const connectLabel = isConnectorReconnect
? newScopes.length > 0
? 'Update access'
: 'Reconnect'
: 'Connect'
const title =
isConnectorReconnect && newScopes.length > 0
? `Update ${providerName} access`
: `${connectLabel} ${providerName}`

return (
<ChipModal open={open} onOpenChange={onOpenChange} srTitle={title}>
Expand All @@ -519,7 +528,11 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
<ChipModalBody>
{!isConnect && (
<p className='text-[var(--text-tertiary)] text-caption'>
The "{props.toolName}" tool requires access to your account.
{isConnectorReconnect
? newScopes.length > 0
? 'Approve the requested permissions to continue syncing.'
: `Continue to ${providerName} to restore this connection.`
: `The "${props.toolName}" tool requires access to your account.`}
</p>
)}

Expand Down Expand Up @@ -651,7 +664,7 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
: undefined
}
primaryAction={{
label: isPending ? 'Connecting...' : 'Connect',
label: isPending ? 'Connecting...' : connectLabel,
onClick: handleConnect,
disabled: isDisabled,
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

import { ChipDropdown } from '@sim/emcn'
import type {
ConnectorDocumentFilter,
ConnectorDocumentsData,
} from '@/lib/api/contracts/knowledge/connectors'

interface ConnectorDocumentStatusFilterProps {
filter: ConnectorDocumentFilter
onFilterChange: (filter: ConnectorDocumentFilter) => void
counts?: ConnectorDocumentsData['counts']
isLoading: boolean
}

export function ConnectorDocumentStatusFilter({
filter,
onFilterChange,
counts = { active: 0, excluded: 0, failed: 0, skipped: 0 },
isLoading,
}: ConnectorDocumentStatusFilterProps) {
return (
<ChipDropdown
aria-label='Document status'
value={filter}
onChange={(value) => {
if (value === 'active' || value === 'excluded' || value === 'failed' || value === 'skipped')
onFilterChange(value)
}}
matchTriggerWidth={false}
options={[
{ value: 'active', label: isLoading ? 'Included' : `Included (${counts.active})` },
{ value: 'excluded', label: isLoading ? 'Excluded' : `Excluded (${counts.excluded})` },
{ value: 'failed', label: isLoading ? 'Failed' : `Failed (${counts.failed})` },
{ value: 'skipped', label: isLoading ? 'Skipped' : `Skipped (${counts.skipped})` },
]}
/>
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client'

import { Chip, ChipDropdown, ChipInput, ChipLink, Skeleton } from '@sim/emcn'
import { Chip, ChipInput, ChipLink, Skeleton } from '@sim/emcn'
import { RefreshCw, Search, SquareArrowUpRight } from '@sim/emcn/icons'
import type { ConnectorDocumentFilter } from '@/lib/api/contracts/knowledge/connectors'
import type { ResourceScope } from '@/lib/core/resource-scope'
import { getDocumentIndexingStatus } from '@/lib/knowledge/documents/types'
import { ConnectorDocumentStatusFilter } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-documents/connector-document-status-filter'
import {
SettingsEmptyState,
SettingsQueryErrorState,
Expand All @@ -25,6 +26,7 @@ interface ConnectorDocumentsProps {
connectorId: string
search?: string
searchControl?: { value: string; onChange: (value: string) => void }
showToolbar?: boolean
progressScope?: ResourceScope
isSearchIndex?: boolean
syncing?: boolean
Expand All @@ -38,6 +40,7 @@ export function ConnectorDocuments({
filter,
search,
searchControl,
showToolbar = true,
progressScope,
isSearchIndex = false,
syncing,
Expand Down Expand Up @@ -79,41 +82,26 @@ export function ConnectorDocuments({
{isSearchIndex && (
<p className='text-[var(--text-body)] text-sm'>Documents you can access</p>
)}
<div className='flex items-center gap-2'>
{searchControl && (
<ChipInput
icon={Search}
placeholder='Search documents...'
value={searchControl.value}
onChange={(event) => searchControl.onChange(event.target.value)}
autoComplete='off'
className='min-w-0 flex-1'
{showToolbar && (
<div className='flex items-center gap-2'>
{searchControl && (
<ChipInput
icon={Search}
placeholder='Search documents...'
value={searchControl.value}
onChange={(event) => searchControl.onChange(event.target.value)}
autoComplete='off'
className='min-w-0 flex-1'
/>
)}
<ConnectorDocumentStatusFilter
filter={filter}
onFilterChange={onFilterChange}
counts={counts}
isLoading={isLoading}
/>
)}
<ChipDropdown
aria-label='Document status'
value={filter}
onChange={(value) => {
if (
value === 'active' ||
value === 'excluded' ||
value === 'failed' ||
value === 'skipped'
)
onFilterChange(value)
}}
matchTriggerWidth={false}
options={[
{ value: 'active', label: isLoading ? 'Included' : `Included (${counts.active})` },
{
value: 'excluded',
label: isLoading ? 'Excluded' : `Excluded (${counts.excluded})`,
},
{ value: 'failed', label: isLoading ? 'Failed' : `Failed (${counts.failed})` },
{ value: 'skipped', label: isLoading ? 'Skipped' : `Skipped (${counts.skipped})` },
]}
/>
</div>
</div>
)}
<div className={RESOURCE_LIST_STACK}>
{query.isError && !query.isFetchNextPageError ? (
<SettingsQueryErrorState
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,96 @@
'use client'

import { useId } from 'react'
import { Checkbox, ChipConfirmModal, ChipModalError, ChipModalField } from '@sim/emcn'
import { SettingsActionChips } from '@/components/settings/settings-header'
import { Fragment, type Ref, useId } from 'react'
import {
type ConnectorActionState,
type ConnectorActionsOptions,
useConnectorActions,
} from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/use-connector-actions'
Checkbox,
Chip,
ChipConfirmModal,
ChipModalError,
ChipModalField,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Tooltip,
} from '@sim/emcn'
import { MoreHorizontal } from '@sim/emcn/icons'
import { orderHeaderActions, type SettingsAction } from '@/components/settings/settings-header'
import type { ConnectorActionState } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/use-connector-actions'
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'

export function ConnectorActions(props: ConnectorActionsOptions) {
const state = useConnectorActions(props)
if (!state.canEdit) return null
interface ConnectorActionsProps {
state: ConnectorActionState
triggerRef?: Ref<HTMLButtonElement>
history?: {
expanded: boolean
contentId: string
onToggle: () => void
}
}

export function ConnectorActions({ state, triggerRef, history }: ConnectorActionsProps) {
if (!state.canEdit && !history) return null
const actions = orderHeaderActions([
...state.actions,
...(history
? [
{
id: 'history',
text: history.expanded ? 'Hide history' : 'Sync history',
onSelect: history.onToggle,
},
]
: []),
])
return (
<div className='flex flex-col gap-2'>
<div className='flex flex-wrap items-center gap-1'>
<SettingsActionChips actions={state.actions} />
</div>
<ConnectorActionFeedback state={state} />
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Chip ref={triggerRef} aria-label='Connection actions' leftIcon={MoreHorizontal} />
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
{actions.map(({ action }, index) => (
<Fragment key={action.id}>
{action.id === 'delete' && index > 0 && <DropdownMenuSeparator />}
<ConnectorActionMenuItem
action={action}
history={action.id === 'history' ? history : undefined}
/>
</Fragment>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}

interface ConnectorActionMenuItemProps {
action: SettingsAction
history?: ConnectorActionsProps['history']
}

function ConnectorActionMenuItem({ action, history }: ConnectorActionMenuItemProps) {
const item = (
<DropdownMenuItem
onSelect={action.onSelect}
disabled={action.disabled}
aria-label={
action.disabled && action.tooltip ? `${action.text} — ${action.tooltip}` : action.text
}
aria-expanded={history?.expanded}
aria-controls={history?.contentId}
Comment thread
waleedlatif1 marked this conversation as resolved.
>
{action.text}
</DropdownMenuItem>
)
return action.tooltip ? (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<span className='block'>{item}</span>
</Tooltip.Trigger>
<Tooltip.Content>{action.tooltip}</Tooltip.Content>
</Tooltip.Root>
) : (
item
)
}

Expand All @@ -30,30 +101,14 @@ interface ConnectorActionFeedbackProps {
export function ConnectorActionFeedback({ state }: ConnectorActionFeedbackProps) {
const deleteDocumentsId = useId()
if (!state.canEdit) return null
const { removal, fullResync } = state
const { removal } = state
return (
<>
{state.error && (
<SettingsEmptyState variant='inline' tone='error'>
{state.error.message}
</SettingsEmptyState>
)}
<ChipConfirmModal
open={fullResync.open}
onOpenChange={fullResync.onOpenChange}
title='Full resync?'
text='Fetch all content again for this connection, including unchanged documents. This can take longer than a regular sync.'
confirm={{
label: 'Full resync',
variant: 'primary',
pending: fullResync.pending,
disabled: fullResync.disabled,
pendingLabel: 'Queuing…',
onClick: fullResync.onConfirm,
}}
>
<ChipModalError>{fullResync.error?.message}</ChipModalError>
</ChipConfirmModal>
<ChipConfirmModal
open={removal.open}
onOpenChange={removal.onOpenChange}
Expand Down
Loading
Loading