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
147 changes: 105 additions & 42 deletions apps/docs/content/docs/search/gitlab.mdx

Large diffs are not rendered by default.

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 @@ -69,7 +69,7 @@ Some member sources offer **Sync documents with**, either directly in setup or u
| --- | --- | --- |
| [Confluence](/search/confluence) | Pages and blog posts | Service account or member accounts; each teammate connects |
| [GitHub](/search/github) | Repository text files | App installation or member indexing; each teammate connects |
| [GitLab](/search/gitlab) | Repository files, wikis, issues, merge requests | Self-managed instance administrator token; no member connection |
| [GitLab](/search/gitlab) | Repository files, wikis, issues, merge requests | Self-managed administrator token, or non-admin token with CSV permissions; no member connection |
| [Gmail](/search/gmail) | Email thread text | Delegated service account or member accounts |
| [Google Calendar](/search/google-calendar) | Meetings | Delegated service account or member accounts |
| [Google Drive](/search/google-drive) | Supported Drive files | Delegated service account or member accounts |
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/sim/app/api/knowledge/[id]/connectors/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const POST = defineInternalJsonRoute({
connectorType: body.connectorType,
credentialId: body.credentialId,
apiKey: body.apiKey,
permissionConfig: body.permissionConfig,
sourceConfig: body.sourceConfig,
syncIntervalMinutes: body.syncIntervalMinutes,
accessMode: body.accessMode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export function OrganizationProviderDetail({ connectorType }: OrganizationProvid
: meta.auth.mode === 'oauth' &&
meta.auth.adminCredentialType === 'service_account'
? 'Service account'
: 'Admin account',
: 'Admin or service account',
!approved
? 'Deactivated'
: !source.enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ describe('Search setup options', () => {
}
)

it.each(SEARCH_SETUP_FIELDS)(
it.each(SEARCH_SETUP_FIELDS.filter(({ connectorType }) => connectorType !== 'gitlab'))(
'keeps $connectorType general knowledge-base fields and sync frequency outside document details',
async ({ connectorType, primary, optional, cap }) => {
await render({
Expand All @@ -814,6 +814,21 @@ describe('Search setup options', () => {
}
)

it('uses GitLab service-account access and token tabs without an access selector in regular KBs', async () => {
await render({
initialConnectorType: 'gitlab',
initialAccessMode: 'workspace',
isSearchIndex: false,
})
expect(document.body.textContent).toContain('Administrator token')
expect(document.body.textContent).toContain('Non-admin token')
expect(document.body.textContent).not.toContain('Connection method')
expect(button('More options')).toHaveAttribute('aria-expanded', 'false')
expect(document.body.textContent).not.toContain('Sync Frequency')
await act(async () => button('More options').click())
expect(document.body.textContent).toContain('Sync Frequency')
})

it('keeps administrator-required fields in the primary form even if metadata marks them optional', async () => {
mocks.credentials = [{ id: 'service', name: 'Indexing account', type: 'service_account' }]
await render({ initialConnectorType: 'google_drive', initialAccessMode: 'admin' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ArrowLeft, ChevronDown, ChevronRight, Plus, Search } from '@sim/emcn/ic
import type { ConnectorData } from '@/lib/api/contracts/knowledge/connectors'
import { type ResourceScope, resourceScopeFields } from '@/lib/core/resource-scope'
import { getIntegrationsForCredentialProvider } from '@/lib/integrations/credential-display'
import { initialConnectorAccessMode } from '@/lib/knowledge/connectors/access-modes'
import {
getCanonicalScopesForProvider,
getProviderIdFromServiceId,
Expand Down Expand Up @@ -62,6 +63,11 @@ import {
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
import { withBrandIcon } from '@/blocks/brand-icon'
import { getConnectorApiKeyConfig, isConnectorCredentialTypeAllowed } from '@/connectors/auth'
import {
GitLabPermissionTabs,
GitLabPermissionUploads,
} from '@/connectors/gitlab/permission-config/fields'
import { useGitLabPermissionForm } from '@/connectors/gitlab/permission-config/use-permission-form'
import { CONNECTOR_META_REGISTRY } from '@/connectors/registry'
import type { ConnectorConfigField, ConnectorMeta } from '@/connectors/types'
import { useCreateConnector } from '@/hooks/queries/kb/connectors'
Expand Down Expand Up @@ -134,14 +140,16 @@ export function AddConnectorModal({
draft?.contentCredentialId ?? null
)
const [access, setAccess] = useState<ConnectorAccessSelection>(() => ({
accessMode:
accessMode: initialConnectorAccessMode(
initialType ? CONNECTOR_META_REGISTRY[initialType] : undefined,
lockedAccessMode ??
(lockConnectorType ? initialAccessMode : draft?.accessMode) ??
(isSearchIndex && initialAccessMode === 'workspace'
? initialType && CONNECTOR_META_REGISTRY[initialType]?.auth.mode === 'apiKey'
? 'admin'
: 'members'
: initialAccessMode),
(lockConnectorType ? initialAccessMode : draft?.accessMode) ??
(isSearchIndex && initialAccessMode === 'workspace'
? initialType && CONNECTOR_META_REGISTRY[initialType]?.auth.mode === 'apiKey'
? 'admin'
: 'members'
: initialAccessMode)
),
}))
const [disabledTagIds, setDisabledTagIds] = useState<Set<string>>(
() => new Set(draft?.disabledTagIds)
Expand All @@ -154,6 +162,7 @@ export function AddConnectorModal({
)
const [showGitHubInstallationModal, setShowGitHubInstallationModal] = useState(false)

const gitlabPermissions = useGitLabPermissionForm()
const [apiKeyValue, setApiKeyValue] = useState('')
const [useApiKey, setUseApiKey] = useState(!isSearchIndex)
const [apiKeyFocused, setApiKeyFocused] = useState(false)
Expand Down Expand Up @@ -182,6 +191,7 @@ export function AddConnectorModal({
? getSearchConnectionLabels(selectedType, access.accessMode)
: undefined
const modalTitle = searchLabels?.title ?? `Configure ${connectorConfig?.name}`
const showGitLabPermissions = selectedType === 'gitlab' && access.accessMode === 'admin'
const isMembersMode = access.accessMode === 'members'
const apiKeyConfig = connectorConfig ? getConnectorApiKeyConfig(connectorConfig.auth) : undefined
const isApiKeyMode =
Expand Down Expand Up @@ -347,7 +357,7 @@ export function AddConnectorModal({
}

const isOptionalSetupField = (field: ConnectorConfigField) =>
isSearchIndex &&
(isSearchIndex || connectorConfig?.supportedAccessModes?.length === 1) &&
field.setupGroup === 'options' &&
Boolean(connectorConfig && !isConnectorFieldRequired(field, connectorConfig, access.accessMode))
const hasOptionalSetupFields = connectorConfig?.configFields.some(
Expand Down Expand Up @@ -433,24 +443,32 @@ export function AddConnectorModal({

const closeSetup = (nextOpen: boolean) => {
if (!nextOpen && setupDraftKey) useConnectorSetupStore.getState().clearDraft(setupDraftKey)
if (!nextOpen) {
gitlabPermissions.reset()
setApiKeyValue('')
}
onOpenChange(nextOpen)
}

const handleSelectType = (type: string) => {
if (setupDraftKey) useConnectorSetupStore.getState().clearDraft(setupDraftKey)
gitlabPermissions.reset()
setSelectedType(type)
setSourceConfig(
isSearchIndex ? { ...CONNECTOR_META_REGISTRY[type]?.searchDefaultSourceConfig } : {}
)
setSelectedCredentialId(null)
setContentCredentialId(null)
setAccess(
isSearchIndex
? {
accessMode: CONNECTOR_META_REGISTRY[type]?.auth.mode === 'apiKey' ? 'admin' : 'members',
}
: WORKSPACE_ACCESS
)
setAccess({
accessMode: initialConnectorAccessMode(
CONNECTOR_META_REGISTRY[type],
isSearchIndex
? CONNECTOR_META_REGISTRY[type]?.auth.mode === 'apiKey'
? 'admin'
: 'members'
: 'workspace'
),
})
setApiKeyValue('')
setUseApiKey(!isSearchIndex)
setApiKeyFocused(false)
Expand All @@ -477,6 +495,7 @@ export function AddConnectorModal({
const canSubmit = Boolean(
connectorConfig &&
hasRequiredCredential &&
(!showGitLabPermissions || gitlabPermissions.complete) &&
hasSearchAccess &&
(access.accessMode !== 'admin' || allowAdmin) &&
(!isMembersMode || allowMembers) &&
Expand Down Expand Up @@ -518,6 +537,7 @@ export function AddConnectorModal({
{
knowledgeBaseId,
connectorType: selectedType,
...(showGitLabPermissions ? { permissionConfig: gitlabPermissions.input } : {}),
accessMode: access.accessMode,
...(isApiKeyMode
? apiKeyValue.trim()
Expand Down Expand Up @@ -625,6 +645,9 @@ export function AddConnectorModal({
</div>
) : connectorConfig ? (
<>
{showGitLabPermissions && (
<GitLabPermissionTabs form={gitlabPermissions} disabled={isCreating} />
)}
{integrationAvailabilityError && (
<ChipModalField type='custom' title='Connection availability'>
<SettingsQueryErrorState
Expand Down Expand Up @@ -774,6 +797,9 @@ export function AddConnectorModal({
}
/>
)}
{showGitLabPermissions && (
<GitLabPermissionUploads form={gitlabPermissions} disabled={isCreating} />
)}

{(hasOptionalSetupFields ||
contentCredentialField ||
Expand All @@ -786,7 +812,9 @@ export function AddConnectorModal({
aria-expanded={showMetadata}
onClick={() => setShowMetadata((visible) => !visible)}
>
{isSearchIndex ? 'More options' : 'Document details (optional)'}
{isSearchIndex || hasOptionalSetupFields
? 'More options'
: 'Document details (optional)'}
</Chip>
</div>
{showMetadata && (
Expand Down Expand Up @@ -841,15 +869,19 @@ export function AddConnectorModal({
</>
)}

{!isSearchIndex && (
{!isSearchIndex && (!hasOptionalSetupFields || showMetadata) && (
<ChipModalField
type='custom'
title='Sync Frequency'
hint={connectorSyncFrequencyHint(
access.accessMode,
syncInterval,
Boolean(contentCredentialId)
)}
hint={
showGitLabPermissions && gitlabPermissions.mode === 'csv'
? undefined
: connectorSyncFrequencyHint(
access.accessMode,
syncInterval,
Boolean(contentCredentialId)
)
}
>
<ButtonGroup
value={String(syncInterval)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,20 @@ describe('connection method selection', () => {
expect(onChange).toHaveBeenCalledWith({ accessMode: 'admin' })
})

it('summarizes a single supported method without a selector', async () => {
it('omits the field when the connector supports only the selected method', async () => {
await render({ connectorConfig: gitlabConnectorMeta, value: { accessMode: 'admin' } })
expect(container.querySelector('[role="radiogroup"]')).toBeNull()
expect(container.textContent).toContain('Admin or service account')
expect(container.textContent).toBe('')
})

it('keeps pending upgrade actions without restoring the redundant selector', async () => {
await render({
connectorConfig: gitlabConnectorMeta,
value: { accessMode: 'admin' },
footer: <button type='button'>Apply changes</button>,
})
expect(container.querySelector('[role="radiogroup"]')).toBeNull()
expect(container.querySelector('button')?.textContent).toBe('Apply changes')
})

it('shows ordinary members a summary without editable or disabled choices', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@sim/emcn'
import type { ConnectorAccessMode } from '@/lib/api/contracts/knowledge/connectors'
import { type ResourceScope, resourceScopeFromOwner } from '@/lib/core/resource-scope'
import { supportsConnectorAccessMode } from '@/lib/knowledge/connectors/access-modes'
import { slackSearchSetupHref } from '@/lib/sim-search/setup-navigation'
import { connectorMemberProvider } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access'
import {
Expand Down Expand Up @@ -135,6 +136,13 @@ export function ConnectorAccessField({
allowed: adminSupported && allowAdmin,
},
]
for (const entry of modes)
entry.allowed &&= supportsConnectorAccessMode(connectorConfig, entry.mode)
if (
connectorConfig.supportedAccessModes?.length === 1 &&
modes.some((entry) => entry.mode === value.accessMode && entry.allowed)
)
return canAdmin && footer ? <div className='px-2'>{footer}</div> : null
/** Keep a retired current method visible so an admin can select an available replacement. */
const visibleModes = modes.filter((entry) => entry.allowed || entry.mode === value.accessMode)
const currentMode = modes.find((entry) => entry.mode === value.accessMode)
Expand Down
Loading
Loading