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/knowledgebase/connectors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'

For workspace Search with each person's source permissions, use the [Search connector guides](/search). This page covers connectors inside general knowledge bases.
For organization Search with each person's source permissions, use the [Search connector guides](/search). This page covers connectors inside general knowledge bases.

Connectors continuously sync documents from external services into your knowledge base, so you never have to upload files manually. New content is added, changed content is re-processed, and deleted content is removed — all automatically.

Expand Down
2 changes: 0 additions & 2 deletions apps/docs/content/docs/search/confluence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ Each teammate completes this identity connection. An existing authorized account
</Step>
</Steps>

For workspace Search, start from **Search → Add source**. Available methods depend on the enabled features.

## Connect member accounts

After an admin configures Confluence, open **Integrations** and select **Connect** beside Confluence. Authorize your account; you do not enter the site's domain or choose its spaces again.
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/github.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Image } from '@/components/ui/image'

GitHub Search indexes repository text files and supported PDF and Office documents on `github.com`. An organization admin can install the GitHub App once and use it to index selected repositories. Each person connects their own GitHub account once to search the repositories they can access. Installing the App does not connect teammates or give them the installer's permissions.

Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar. Installation indexing is available for organization Search. For workspace Search, use **Search → Add source** with member accounts or a dedicated user account.
Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar. Installation indexing is available for organization Search.

## Before you start

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 @@ -9,7 +9,7 @@ import { Image } from '@/components/ui/image'

Connect one GitLab project per source. Choose an **Administrator token** to refresh permissions from GitLab, or a **Non-admin token** with two CSV files to manage access in Sim. Teammates do not connect individual GitLab accounts.

Organization admins set up sources in **Settings → Sources**. For workspace Search, use **Search → Add source**. The same token paths are available when adding a GitLab connector to a regular knowledge base.
Organization admins set up sources in **Settings → Sources**. The same token paths are available when adding a GitLab connector to a regular knowledge base.

## Choose a token path

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/gmail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Image } from '@/components/ui/image'

Search email threads from your Gmail account. Members can connect their own accounts, or an administrator can index Google Workspace mailboxes with a service account. In either case, each mailbox stays private to its owner.

Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar. For workspace Search, use **Search → Add source** instead.
Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar.

## Choose your setup

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/google-calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Image } from '@/components/ui/image'

Search meetings and event details available to your Google account. Members can connect personal accounts, or a Google Workspace administrator can configure a central service-account crawl. Both paths keep each person's own view of events separate.

Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar. For workspace Search, use **Search → Add source** instead.
Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar.

## Choose your setup

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/search/google-drive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Image } from '@/components/ui/image'

Search Google Docs, Sheets, Slides, and supported files in Drive. Members can connect their own accounts after an admin allows Google Drive, or an admin can configure a central service-account crawl.

Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar. For workspace Search, use **Search → Add source** instead.
Admin setup uses your organization's **Settings → Sources** page. Teammates connect from **Integrations** in the main sidebar.

## Choose your setup

Expand Down
4 changes: 0 additions & 4 deletions apps/docs/content/docs/search/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@ To deactivate an entire integration, open it from **Settings → Sources**, sele

To use these sources from Claude, Codex, Claude Code, Cursor, or another compatible app, open **Settings → Search MCP**. Each person signs in with their own Sim account. The server provides `search`, `read_document`, and `chat`; `chat` starts a new private Sim conversation. See [Search MCP](/search/mcp) for app setup, permissions, and limits.

## Existing workspace Search

Workspace Search remains separate. Workspace admins add sources through **Search → Add source**; the member-account action is **Create & Invite**. Teammates need workspace access and connect from its source list. Organization Search does not automatically include workspace sources or grant access to workspace content.

## Check that it works

1. Let the first sync finish, then search for a distinctive phrase in a document you can open in the source.
Expand Down
2 changes: 0 additions & 2 deletions apps/docs/content/docs/search/jira.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ Each teammate follows this step. Sim reuses an existing authorized account where
</Step>
</Steps>

For workspace Search, start from **Search → Add source**; its source-creation button is **Create & Invite**. Search manages the sync schedule and hides item limits.

## Connect before a source exists

After an admin approves Jira, a teammate can select **Connect** on the Jira row in **Integrations**. To add another site or project selection later, open the Jira row's actions menu (**…**) and select **Add projects**:
Expand Down
2 changes: 0 additions & 2 deletions apps/docs/content/docs/search/slack.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ Compare **OAuth & Permissions → Scopes → User Token Scopes** with the table

Existing workflow account pools retain their configured permissions; preserve those scopes when updating the shared app.

For existing **workspace** Search, use **Search → Add source → Slack**. That flow uses the custom-bot wizard and **Connected accounts → Access → Search documents**, which requests read-only channel, DM, and identity scopes. Its bot installation is separate from the organization setup described here.

See Slack's [app manifest reference](https://docs.slack.dev/reference/app-manifest/) and [user token access model](https://docs.slack.dev/authentication/tokens/).

## Troubleshooting
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/app/api/knowledge/member-connectors/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export const GET = defineInternalJsonRoute({
contract: listWorkspaceMemberConnectorsContract,
auth: internalSessionAuth,
operation: knowledgeOperations.listWorkspaceMemberConnectors,
rateLimit: internalRateLimits.none({ reason: 'One small read per visit to the Search tab' }),
rateLimit: internalRateLimits.none({
reason: 'Preserve existing internal connector listing behavior',
}),
errorPolicy: internalKnowledgeErrorPolicies.connectors,
mapInput: ({ query }) => ({ workspaceId: query.workspaceId }),
useCase: listWorkspaceMemberConnectors,
Expand Down
10 changes: 5 additions & 5 deletions apps/sim/app/credential-groups/enroll/[token]/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ describe('focused Search enrollment', () => {
expect(document.querySelector('form')).toBeNull()
expect(
Array.from(document.querySelectorAll('a'))
.find((link) => link.textContent === 'Return to Search')
.find((link) => link.textContent === 'Open knowledge bases')
?.getAttribute('href')
).toBe('/workspace/canonical-workspace/search')
).toBe('/workspace/canonical-workspace/knowledge')
expect(document.body.textContent).not.toContain('Setup guide')
expect(mocks.read).toHaveBeenCalledWith({ principal, input: { optionId: 'site-two' } })
})
Expand All @@ -243,7 +243,7 @@ describe('focused Search enrollment', () => {
expect(document.body.textContent).toContain('Ask an admin')
expect(oauthLinks()).toHaveLength(0)
expect(document.querySelector('form')).toBeNull()
expect(document.body.textContent).toContain('Return to Search')
expect(document.body.textContent).toContain('Open knowledge bases')
}
)

Expand All @@ -255,7 +255,7 @@ describe('focused Search enrollment', () => {
expect(document.body.textContent).toContain('Connection unavailable')
expect(document.body.textContent).toContain('Ask an admin')
expect(document.querySelector('a')?.getAttribute('href')).toBe(
'/workspace/canonical-workspace/search'
'/workspace/canonical-workspace/knowledge'
)
})

Expand All @@ -274,7 +274,7 @@ describe('focused Search enrollment', () => {
expect(document.querySelector('h1')?.textContent).toBe('Confluence connected')
expect(oauthLinks()).toHaveLength(0)
expect(document.querySelector('form')).toBeNull()
expect(document.body.textContent).toContain('Return to Search')
expect(document.body.textContent).toContain('Open knowledge bases')
})

it('does not treat a success query marker as a connected account', async () => {
Expand Down
12 changes: 8 additions & 4 deletions apps/sim/app/credential-groups/enroll/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,12 @@ export default async function CredentialGroupEnrollmentPage({
const canReturnToSearch =
returnToSearch &&
('canSearch' in enrollmentResult ? enrollmentResult.canSearch : !principal.organizationId)
const returnHref = canReturnToSearch ? searchReturnPath(principal) : APP_ENTRY_PATH
const returnLabel = canReturnToSearch ? 'Return to Search' : 'Open Sim'
const returnHref = canReturnToSearch ? sourceReturnPath(principal) : APP_ENTRY_PATH
const returnLabel = canReturnToSearch
? principal.organizationId
? 'Return to Search'
: 'Open knowledge bases'
: 'Open Sim'
if (!enrollment)
return <UnavailableSearchConnection returnHref={returnHref} returnLabel={returnLabel} />

Expand Down Expand Up @@ -328,9 +332,9 @@ export default async function CredentialGroupEnrollmentPage({
)
}

function searchReturnPath(owner: ResourceOwner): string {
function sourceReturnPath(owner: ResourceOwner): string {
const scope = resourceScopeFromOwner(owner)
return scope.kind === 'workspace'
? `/workspace/${encodeURIComponent(scope.workspaceId)}/search`
? `/workspace/${encodeURIComponent(scope.workspaceId)}/knowledge`
: organizationRoutes(scope.organizationId).search
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,9 @@ vi.mock('@/app/workspace/[workspaceId]/integrations/components/integrations-show
IntegrationTile: () => null,
}))

import type { SearchSourceSummary } from '@/lib/api/contracts/knowledge/connectors'
import { DisconnectAccountMenu } from '@/app/o/[organizationId]/integrations/disconnect-account-menu'
import { SearchSourceRow } from '@/app/workspace/[workspaceId]/search/components/search-source-row'

const accounts = [{ credentialId: 'my-gmail', displayName: 'me@example.test' }]
const source: SearchSourceSummary = {
knowledgeBaseId: 'kb',
connectorId: 'gmail',
connectorType: 'gmail',
sourceDescription: '',
accessMode: 'members',
availability: 'available',
enabled: true,
isSyncing: true,
lastSyncAt: null,
hasSyncError: false,
viewerDocumentCount: 0,
viewerFailedDocumentCount: 0,
viewerEmailVerified: true,
connectionRequired: true,
viewerMembership: 'connected',
viewerAccounts: accounts,
}

describe('personal integration disconnect', () => {
let root: Root
let container: HTMLDivElement
Expand All @@ -56,25 +35,10 @@ describe('personal integration disconnect', () => {
container.remove()
vi.unstubAllGlobals()
})
async function render(overrides: Partial<SearchSourceSummary> = {}) {
async function render() {
await act(async () =>
root.render(
<SearchSourceRow
source={{ ...source, ...overrides } as SearchSourceSummary}
scope={{ kind: 'organization', organizationId: 'org' }}
canAdmin={false}
available={false}
waiting
isPending
onConnect={vi.fn()}
accountActions={
<DisconnectAccountMenu
organizationId='org'
integrationName='Gmail'
accounts={accounts}
/>
}
/>
<DisconnectAccountMenu organizationId='org' integrationName='Gmail' accounts={accounts} />
)
)
}
Expand All @@ -96,15 +60,8 @@ describe('personal integration disconnect', () => {
)!
}

it.each([
['indexing', {}],
['failed', { hasSyncError: true }],
['paused', { enabled: false }],
['deactivated', { approved: false }],
['reconnect', { viewerMembership: 'needs_reauth' }],
['unavailable', { availability: 'unavailable', viewerMembership: null }],
] as const)('allows disconnect while %s without requiring admin access', async (_, overrides) => {
await render(overrides)
it('requires confirmation before disconnecting an account', async () => {
await render()
await openDisconnect()
expect(document.body.textContent).toContain(
'Disconnect me@example.test from all Gmail connections in this organization.'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import { Chip, ChipLink } from '@sim/emcn'
import { organizationRoutes } from '@/lib/navigation/paths'
import { connectorDisplayName } from '@/lib/sim-search/connectors'
import { getSearchSourceStatus } from '@/lib/sim-search/source-status'
import { DisconnectAccountMenu } from '@/app/o/[organizationId]/integrations/disconnect-account-menu'
import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase'
import { getSearchSourceStatus } from '@/app/workspace/[workspaceId]/search/components/search-source-row-status'
import type { RowAction } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu'
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
import { CONNECTOR_META_REGISTRY } from '@/connectors/registry'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ vi.mock('@/hooks/queries/search-integrations', () => ({
vi.mock('@/hooks/use-permission-config', () => ({
usePermissionConfig: mocks.availability,
}))
vi.mock('@/app/workspace/[workspaceId]/search/components/search-source-setup', () => ({
vi.mock('@/app/o/[organizationId]/settings/components/integrations/search-source-setup', () => ({
SearchSourceSetup: () => null,
}))
vi.mock('@/app/o/[organizationId]/settings/components/integrations/slack-account-setup', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ import { useQueryStates } from 'nuqs'
import { SettingsPanel } from '@/components/settings/settings-panel'
import { organizationRoutes } from '@/lib/navigation/paths'
import { getConnectorAccessAvailability, SEARCH_SOURCE_TYPES } from '@/lib/sim-search/connectors'
import { searchSetupAccessParam, searchSetupParam } from '@/lib/sim-search/search-params'
import { useOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider'
import { AddOrganizationSourceModal } from '@/app/o/[organizationId]/settings/components/integrations/add-organization-source-modal'
import { organizationSearchStatusLabel } from '@/app/o/[organizationId]/settings/components/integrations/organization-search-status'
import { SearchSourceSetup } from '@/app/o/[organizationId]/settings/components/integrations/search-source-setup'
import { OrganizationSlackAccountSetup } from '@/app/o/[organizationId]/settings/components/integrations/slack-account-setup'
import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase'
import { SearchSourceSetup } from '@/app/workspace/[workspaceId]/search/components/search-source-setup'
import {
searchSetupAccessParam,
searchSetupParam,
} from '@/app/workspace/[workspaceId]/search/search-params'
import {
SettingsEmptyState,
SettingsQueryErrorState,
Expand Down
Loading
Loading