Feat: Bitbucket connector (#12332)

### What problem does this PR solve?

Feat: Bitbucket connector NOT READY TO MERGE

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
Magicbook1108
2025-12-31 17:18:30 +08:00
committed by GitHub
parent 6a664fea3b
commit 7d4d687dde
26 changed files with 1294 additions and 555 deletions

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg xmlns="http://www.w3.org/2000/svg"
aria-label="Bitbucket" role="img"
viewBox="0 0 512 512"><rect
width="512" height="512"
rx="15%"
fill="#ffffff"/><path fill="#2684ff" d="M422 130a10 10 0 00-9.9-11.7H100.5a10 10 0 00-10 11.7L136 409a10 10 0 009.9 8.4h221c5 0 9.2-3.5 10 -8.4L422 130zM291 316.8h-69.3l-18.7-98h104.8z"/><path fill="url(#a)" d="M59.632 25.2H40.94l-3.1 18.3h-13v18.9H52c1 0 1.7-.7 1.8-1.6l5.8-35.6z" transform="translate(89.8 85) scale(5.3285)"/><linearGradient id="a" x2="1" gradientTransform="rotate(141 22.239 22.239) scale(31.4)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient></svg>

After

Width:  |  Height:  |  Size: 803 B

View File

@ -947,6 +947,19 @@ Beispiel: Virtual Hosted Style`,
'Laden Sie das OAuth-JSON hoch, das von der Google Console generiert wurde. Wenn es nur Client-Anmeldeinformationen enthält, führen Sie die browserbasierte Überprüfung einmal durch, um langlebige Refresh-Token zu erstellen.',
dropboxDescription:
'Verbinden Sie Ihre Dropbox, um Dateien und Ordner von einem ausgewählten Konto zu synchronisieren.',
bitbucketDescription:
'Bitbucket verbinden, um PR-Inhalte zu synchronisieren.',
zendeskDescription:
'Verbinden Sie Ihr Zendesk, um Tickets, Artikel und andere Inhalte zu synchronisieren.',
bitbucketTopWorkspaceTip:
'Der zu indizierende Bitbucket-Workspace (z. B. "atlassian" aus https://bitbucket.org/atlassian/workspace )',
bitbucketWorkspaceTip:
'Dieser Connector indiziert alle Repositories im Workspace.',
bitbucketProjectsTip: 'Kommagetrennte Projekt-Keys, z. B.: PROJ1,PROJ2',
bitbucketRepositorySlugsTip:
'Kommagetrennte Repository-Slugs, z. B.: repo-one,repo-two',
connectorNameTip:
'Geben Sie einen aussagekräftigen Namen für den Connector an',
boxDescription:
'Verbinden Sie Ihr Box-Laufwerk, um Dateien und Ordner zu synchronisieren.',
githubDescription:

View File

@ -879,6 +879,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
cropImage: 'Crop image',
selectModelPlaceholder: 'Select model',
configureModelTitle: 'Configure model',
connectorNameTip: 'A descriptive name for the connector',
confluenceIsCloudTip:
'Check if this is a Confluence Cloud instance, uncheck for Confluence Server/Data Center',
confluenceWikiBaseUrlTip:
@ -923,7 +924,9 @@ Example: Virtual Hosted Style`,
google_driveTokenTip:
'Upload the OAuth token JSON generated from the OAuth helper or Google Cloud Console. You may also upload a client_secret JSON from an "installed" or "web" application. If this is your first sync, a browser window will open to complete the OAuth consent. If the JSON already contains a refresh token, it will be reused automatically.',
google_drivePrimaryAdminTip:
'Email address that has access to the Drive content being synced.',
'Email address that has access to the Drive content being synced',
zendeskDescription:
'Connect your Zendesk to sync tickets, articles, and other content.',
google_driveMyDriveEmailsTip:
'Comma-separated emails whose "My Drive" contents should be indexed (include the primary admin).',
google_driveSharedFoldersTip:
@ -934,7 +937,16 @@ Example: Virtual Hosted Style`,
'Upload the OAuth JSON generated from Google Console. If it only contains client credentials, run the browser-based verification once to mint long-lived refresh tokens.',
dropboxDescription:
'Connect your Dropbox to sync files and folders from a chosen account.',
bitbucketDescription: 'Connect Bitbucket to sync PR content.',
bitbucketTopWorkspaceTip:
'The Bitbucket workspace to index (e.g., "atlassian" from https://bitbucket.org/atlassian/workspace ).',
bitbucketRepositorySlugsTip:
'Comma separated repository slugs. E.g., repo-one,repo-two',
bitbucketProjectsTip: 'Comma separated project keys. E.g., PROJ1,PROJ2',
bitbucketWorkspaceTip:
'This connector will index all repositories in the workspace.',
boxDescription: 'Connect your Box drive to sync files and folders.',
githubDescription:
'Connect GitHub to sync pull requests and issues for retrieval.',
airtableDescription:

View File

@ -731,6 +731,7 @@ export default {
newDocs: 'Новые документы',
timeStarted: 'Время начала',
log: 'Лог',
connectorNameTip: 'Укажите понятное имя для коннектора',
confluenceDescription:
'Интегрируйте ваше рабочее пространство Confluence для поиска документации.',
s3Description:
@ -747,6 +748,18 @@ export default {
'Синхронизируйте страницы и базы данных из Notion для извлечения знаний.',
boxDescription:
'Подключите ваш диск Box для синхронизации файлов и папок.',
bitbucketDescription:
'Подключите Bitbucket для синхронизации содержимого PR.',
zendeskDescription:
'Подключите Zendesk для синхронизации тикетов, статей и другого контента.',
bitbucketTopWorkspaceTip:
'Рабочее пространство Bitbucket для индексации (например, "atlassian" из https://bitbucket.org/atlassian/workspace )',
bitbucketWorkspaceTip:
'Этот коннектор проиндексирует все репозитории в рабочем пространстве.',
bitbucketProjectsTip:
'Ключи проектов через запятую, например: PROJ1,PROJ2',
bitbucketRepositorySlugsTip:
'Слоги репозиториев через запятую, например: repo-one,repo-two',
githubDescription:
'Подключите GitHub для синхронизации содержимого Pull Request и Issue для поиска.',
airtableDescription:

View File

@ -726,6 +726,16 @@ export default {
view: '查看',
modelsToBeAddedTooltip:
'若您的模型供應商未列於此處,但宣稱與 OpenAI 相容可透過選擇「OpenAI-API-compatible」卡片來設定相關模型。',
dropboxDescription: '連接 Dropbox同步指定帳號下的文件與文件夾。',
bitbucketDescription: '連接 Bitbucket同步 PR 內容。',
zendeskDescription: '連接 Zendesk同步工單、文章及其他內容。',
bitbucketTopWorkspaceTip:
'要索引的 Bitbucket 工作區例如https://bitbucket.org/atlassian/workspace 中的 "atlassian"',
bitbucketWorkspaceTip: '此連接器將索引工作區下的所有倉庫。',
bitbucketRepositorySlugsTip:
'以英文逗號分隔的倉庫 slug例如repo-one,repo-two',
bitbucketProjectsTip: '以英文逗號分隔的項目鍵例如PROJ1,PROJ2',
connectorNameTip: '為連接器填寫一個有意義的名稱',
},
message: {
registered: '註冊成功',

View File

@ -53,6 +53,7 @@ export default {
noData: '暂无数据',
bedrockCredentialsHint:
'提示Access Key / Secret Key 可留空,以启用 AWS IAM 自动验证。',
zendeskDescription: '连接 Zendesk同步工单、文章及其他内容。',
promptPlaceholder: '请输入或使用 / 快速插入变量。',
selected: '已选择',
},
@ -864,6 +865,14 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
'请上传由 Google Console 生成的 OAuth JSON。如果仅包含 client credentials请通过浏览器授权一次以获取长期有效的刷新 Token。',
dropboxDescription: '连接 Dropbox同步指定账号下的文件与文件夹。',
boxDescription: '连接你的 Box 云盘以同步文件和文件夹。',
bitbucketDescription: '连接 Bitbucket同步 PR 内容。',
bitbucketTopWorkspaceTip:
'要索引的 Bitbucket 工作区例如https://bitbucket.org/atlassian/workspace 中的 "atlassian"',
bitbucketWorkspaceTip: '该连接器将索引工作区下的所有仓库。',
bitbucketProjectsTip: '用英文逗号分隔的项目 key例如PROJ1,PROJ2',
bitbucketRepositorySlugsTip:
'用英文逗号分隔的仓库 slug例如repo-one,repo-two',
connectorNameTip: '为连接器命名',
githubDescription:
'连接 GitHub可同步 Pull Request 与 Issue 内容用于检索。',
airtableDescription: '连接 Airtable同步指定工作区下指定表格中的文件。',

View File

@ -1,247 +0,0 @@
import { useEffect, useMemo, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Input } from '@/components/ui/input';
import { Segmented } from '@/components/ui/segmented';
import { t } from 'i18next';
// UI-only auth modes for S3
// access_key: Access Key ID + Secret
// iam_role: only Role ARN
// assume_role: no input fields (uses environment role)
type AuthMode = 'access_key' | 'iam_role' | 'assume_role';
type BlobMode = 's3' | 's3_compatible';
const modeOptions = [
{ label: 'S3', value: 's3' },
{ label: 'S3 Compatible', value: 's3_compatible' },
];
const authOptions = [
{ label: 'Access Key', value: 'access_key' },
{ label: 'IAM Role', value: 'iam_role' },
{ label: 'Assume Role', value: 'assume_role' },
];
const addressingOptions = [
{ label: 'Virtual Hosted Style', value: 'virtual' },
{ label: 'Path Style', value: 'path' },
];
const deriveInitialAuthMode = (credentials: any): AuthMode => {
const authMethod = credentials?.authentication_method;
if (authMethod === 'iam_role') return 'iam_role';
if (authMethod === 'assume_role') return 'assume_role';
if (credentials?.aws_role_arn) return 'iam_role';
if (credentials?.aws_access_key_id || credentials?.aws_secret_access_key)
return 'access_key';
return 'access_key';
};
const deriveInitialMode = (bucketType?: string): BlobMode =>
bucketType === 's3_compatible' ? 's3_compatible' : 's3';
const BlobTokenField = () => {
const form = useFormContext();
const credentials = form.watch('config.credentials');
const watchedBucketType = form.watch('config.bucket_type');
const [mode, setMode] = useState<BlobMode>(
deriveInitialMode(watchedBucketType),
);
const [authMode, setAuthMode] = useState<AuthMode>(() =>
deriveInitialAuthMode(credentials),
);
// Keep bucket_type in sync with UI mode
useEffect(() => {
const nextMode = deriveInitialMode(watchedBucketType);
setMode((prev) => (prev === nextMode ? prev : nextMode));
}, [watchedBucketType]);
useEffect(() => {
form.setValue('config.bucket_type', mode, { shouldDirty: true });
// Default addressing style for compatible mode
if (
mode === 's3_compatible' &&
!form.getValues('config.credentials.addressing_style')
) {
form.setValue('config.credentials.addressing_style', 'virtual', {
shouldDirty: false,
});
}
if (mode === 's3_compatible' && authMode !== 'access_key') {
setAuthMode('access_key');
}
// Persist authentication_method for backend
const nextAuthMethod: AuthMode =
mode === 's3_compatible' ? 'access_key' : authMode;
form.setValue('config.credentials.authentication_method', nextAuthMethod, {
shouldDirty: true,
});
// Clear errors for fields that are not relevant in the current mode/auth selection
const inactiveFields: string[] = [];
if (mode === 's3_compatible') {
inactiveFields.push('config.credentials.aws_role_arn');
} else {
if (authMode === 'iam_role') {
inactiveFields.push('config.credentials.aws_access_key_id');
inactiveFields.push('config.credentials.aws_secret_access_key');
}
if (authMode === 'assume_role') {
inactiveFields.push('config.credentials.aws_access_key_id');
inactiveFields.push('config.credentials.aws_secret_access_key');
inactiveFields.push('config.credentials.aws_role_arn');
}
}
if (inactiveFields.length) {
form.clearErrors(inactiveFields as any);
}
}, [form, mode, authMode]);
const isS3 = mode === 's3';
const requiresAccessKey =
authMode === 'access_key' || mode === 's3_compatible';
const requiresRoleArn = isS3 && authMode === 'iam_role';
// Help text for assume role (no inputs)
const assumeRoleNote = useMemo(
() => t('No credentials required. Uses the default environment role.'),
[t],
);
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<div className="text-sm text-text-secondary">Mode</div>
<Segmented
options={modeOptions}
value={mode}
onChange={(val) => setMode(val as BlobMode)}
className="w-full"
itemClassName="flex-1 justify-center"
/>
</div>
{isS3 && (
<div className="flex flex-col gap-2">
<div className="text-sm text-text-secondary">Authentication</div>
<Segmented
options={authOptions}
value={authMode}
onChange={(val) => setAuthMode(val as AuthMode)}
className="w-full"
itemClassName="flex-1 justify-center"
/>
</div>
)}
{requiresAccessKey && (
<RAGFlowFormItem
name="config.credentials.aws_access_key_id"
label="AWS Access Key ID"
required={requiresAccessKey}
rules={{
validate: (val) =>
requiresAccessKey
? Boolean(val) || 'Access Key ID is required'
: true,
}}
>
{(field) => (
<Input {...field} placeholder="AKIA..." autoComplete="off" />
)}
</RAGFlowFormItem>
)}
{requiresAccessKey && (
<RAGFlowFormItem
name="config.credentials.aws_secret_access_key"
label="AWS Secret Access Key"
required={requiresAccessKey}
rules={{
validate: (val) =>
requiresAccessKey
? Boolean(val) || 'Secret Access Key is required'
: true,
}}
>
{(field) => (
<Input
{...field}
type="password"
placeholder="****************"
autoComplete="new-password"
/>
)}
</RAGFlowFormItem>
)}
{requiresRoleArn && (
<RAGFlowFormItem
name="config.credentials.aws_role_arn"
label="Role ARN"
required={requiresRoleArn}
tooltip="The role will be assumed by the runtime environment."
rules={{
validate: (val) =>
requiresRoleArn ? Boolean(val) || 'Role ARN is required' : true,
}}
>
{(field) => (
<Input
{...field}
placeholder="arn:aws:iam::123456789012:role/YourRole"
autoComplete="off"
/>
)}
</RAGFlowFormItem>
)}
{isS3 && authMode === 'assume_role' && (
<div className="text-sm text-text-secondary bg-bg-card border border-border-button rounded-md px-3 py-2">
{assumeRoleNote}
</div>
)}
{mode === 's3_compatible' && (
<div className="flex flex-col gap-4">
<RAGFlowFormItem
name="config.credentials.addressing_style"
label="Addressing Style"
tooltip={t('setting.S3CompatibleAddressingStyleTip')}
required={false}
>
{(field) => (
<SelectWithSearch
triggerClassName="!shrink"
options={addressingOptions}
value={field.value || 'virtual'}
onChange={(val) => field.onChange(val)}
/>
)}
</RAGFlowFormItem>
<RAGFlowFormItem
name="config.credentials.endpoint_url"
label="Endpoint URL"
required={false}
tooltip={t('setting.S3CompatibleEndpointUrlTip')}
>
{(field) => (
<Input
{...field}
placeholder="https://fsn1.your-objectstorage.com"
autoComplete="off"
/>
)}
</RAGFlowFormItem>
</div>
)}
</div>
);
};
export default BlobTokenField;

View File

@ -131,7 +131,6 @@ const BoxTokenField = ({ value, onChange }: BoxTokenFieldProps) => {
const finalValue: Record<string, any> = {
...rest,
// 确保客户端配置字段有值(优先后端返回,其次当前输入)
client_id: rest.client_id ?? clientId.trim(),
client_secret: rest.client_secret ?? clientSecret.trim(),
};
@ -146,8 +145,6 @@ const BoxTokenField = ({ value, onChange }: BoxTokenFieldProps) => {
finalValue.authorization_code = code;
}
// access_token / refresh_token 由后端返回,已在 ...rest 中带上,无需额外 state
onChange(JSON.stringify(finalValue));
message.success('Box authorization completed.');
clearWebState();

View File

@ -1,200 +0,0 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { ControllerRenderProps, useFormContext } from 'react-hook-form';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import { debounce } from 'lodash';
/* ---------------- Token Field ---------------- */
export type ConfluenceTokenFieldProps = ControllerRenderProps & {
fieldType: 'username' | 'token';
placeholder?: string;
disabled?: boolean;
};
const ConfluenceTokenField = ({
fieldType,
value,
onChange,
placeholder,
disabled,
...rest
}: ConfluenceTokenFieldProps) => {
return (
<div className="flex w-full flex-col gap-2">
<Input
className="w-full"
type={fieldType === 'token' ? 'password' : 'text'}
value={value ?? ''}
onChange={(e) => onChange(e.target.value)}
placeholder={
placeholder ||
(fieldType === 'token'
? 'Enter your Confluence access token'
: 'Confluence username or email')
}
disabled={disabled}
{...rest}
/>
</div>
);
};
/* ---------------- Indexing Mode Field ---------------- */
type ConfluenceIndexingMode = 'everything' | 'space' | 'page';
export type ConfluenceIndexingModeFieldProps = ControllerRenderProps;
export const ConfluenceIndexingModeField = (
fieldProps: ControllerRenderProps,
) => {
const { value, onChange, disabled } = fieldProps;
const [mode, setMode] = useState<ConfluenceIndexingMode>(
value || 'everything',
);
const { watch, setValue } = useFormContext();
useEffect(() => setMode(value), [value]);
const spaceValue = watch('config.space');
const pageIdValue = watch('config.page_id');
const indexRecursively = watch('config.index_recursively');
useEffect(() => {
if (!value) onChange('everything');
}, [value, onChange]);
const handleModeChange = useCallback(
(nextMode?: string) => {
let normalized: ConfluenceIndexingMode = 'everything';
if (nextMode) {
normalized = nextMode as ConfluenceIndexingMode;
setMode(normalized);
onChange(normalized);
} else {
setMode(mode);
normalized = mode;
onChange(mode);
// onChange(mode);
}
if (normalized === 'everything') {
setValue('config.space', '');
setValue('config.page_id', '');
setValue('config.index_recursively', false);
} else if (normalized === 'space') {
setValue('config.page_id', '');
setValue('config.index_recursively', false);
} else if (normalized === 'page') {
setValue('config.space', '');
}
},
[mode, onChange, setValue],
);
const debouncedHandleChange = useMemo(
() =>
debounce(() => {
handleModeChange();
}, 300),
[handleModeChange],
);
return (
<div className="w-full rounded-lg border border-border-button bg-bg-card p-4 space-y-4">
<div className="flex items-center gap-2 text-sm font-medium text-text-secondary">
{INDEX_MODE_OPTIONS.map((option) => {
const isActive = option.value === mode;
return (
<button
key={option.value}
type="button"
disabled={disabled}
onClick={() => handleModeChange(option.value)}
className={cn(
'flex-1 rounded-lg border px-3 py-2 transition-all',
'border-transparent bg-transparent text-text-secondary hover:border-border-button hover:bg-bg-card-secondary',
isActive &&
'border-border-button bg-background text-primary shadow-sm',
)}
>
{option.label}
</button>
);
})}
</div>
{mode === 'everything' && (
<p className="text-sm text-text-secondary">
This connector will index all pages the provided credentials have
access to.
</p>
)}
{mode === 'space' && (
<div className="space-y-2">
<div className="text-sm font-semibold text-text-primary">
Space Key
</div>
<Input
className="w-full"
value={spaceValue ?? ''}
onChange={(e) => {
const value = e.target.value;
setValue('config.space', value);
debouncedHandleChange();
}}
placeholder="e.g. KB"
disabled={disabled}
/>
<p className="text-xs text-text-secondary">
The Confluence space key to index.
</p>
</div>
)}
{mode === 'page' && (
<div className="space-y-2">
<div className="text-sm font-semibold text-text-primary">Page ID</div>
<Input
className="w-full"
value={pageIdValue ?? ''}
onChange={(e) => {
setValue('config.page_id', e.target.value);
debouncedHandleChange();
}}
placeholder="e.g. 123456"
disabled={disabled}
/>
<p className="text-xs text-text-secondary">
The Confluence page ID to index.
</p>
<div className="flex items-center gap-2 pt-2">
<Checkbox
checked={Boolean(indexRecursively)}
onCheckedChange={(checked) => {
setValue('config.index_recursively', Boolean(checked));
debouncedHandleChange();
}}
disabled={disabled}
/>
<span className="text-sm text-text-secondary">
Index child pages recursively
</span>
</div>
</div>
)}
</div>
);
};
const INDEX_MODE_OPTIONS = [
{ label: 'Everything', value: 'everything' },
{ label: 'Space', value: 'space' },
{ label: 'Page', value: 'page' },
];
export default ConfluenceTokenField;

View File

@ -0,0 +1,83 @@
import { FilterFormField, FormFieldType } from '@/components/dynamic-form';
import { TFunction } from 'i18next';
export const bitbucketConstant = (t: TFunction) => [
{
label: 'Bitbucket Account Email',
name: 'config.credentials.bitbucket_account_email',
type: FormFieldType.Email,
required: true,
},
{
label: 'Bitbucket API Token',
name: 'config.credentials.bitbucket_api_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Workspace',
name: 'config.workspace',
type: FormFieldType.Text,
required: true,
tooltip: t('setting.bitbucketTopWorkspaceTip'),
},
{
label: 'Index Mode',
name: 'config.index_mode',
type: FormFieldType.Segmented,
options: [
{ label: 'Repositories', value: 'repositories' },
{ label: 'Project(s)', value: 'projects' },
{ label: 'Workspace', value: 'workspace' },
],
},
{
label: 'Repository Slugs',
name: 'config.repository_slugs',
type: FormFieldType.Text,
customValidate: (val: string, formValues: any) => {
const index_mode = formValues?.config?.index_mode;
if (!val && index_mode === 'repositories') {
return 'Repository Slugs is required';
}
return true;
},
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
return index_mode === 'repositories';
},
tooltip: t('setting.bitbucketRepositorySlugsTip'),
},
{
label: 'Projects',
name: 'config.projects',
type: FormFieldType.Text,
customValidate: (val: string, formValues: any) => {
const index_mode = formValues?.config?.index_mode;
if (!val && index_mode === 'projects') {
return 'Projects is required';
}
return true;
},
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
console.log('formValues.config', formValues?.config);
return index_mode === 'projects';
},
tooltip: t('setting.bitbucketProjectsTip'),
},
{
name: FilterFormField + '.tip',
label: ' ',
type: FormFieldType.Custom,
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
return index_mode === 'workspace';
},
render: () => (
<div className="text-sm text-text-secondary bg-bg-card border border-border-button rounded-md px-3 py-2">
{t('setting.bitbucketWorkspaceTip')}
</div>
),
},
];

View File

@ -0,0 +1,121 @@
import { FilterFormField, FormFieldType } from '@/components/dynamic-form';
import { TFunction } from 'i18next';
export const confluenceConstant = (t: TFunction) => [
{
label: 'Confluence Username',
name: 'config.credentials.confluence_username',
type: FormFieldType.Text,
required: true,
tooltip: t('setting.connectorNameTip'),
},
{
label: 'Confluence Access Token',
name: 'config.credentials.confluence_access_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Wiki Base URL',
name: 'config.wiki_base',
type: FormFieldType.Text,
required: false,
tooltip: t('setting.confluenceWikiBaseUrlTip'),
},
{
label: 'Is Cloud',
name: 'config.is_cloud',
type: FormFieldType.Checkbox,
required: false,
tooltip: t('setting.confluenceIsCloudTip'),
},
{
label: 'Index Mode',
name: 'config.index_mode',
type: FormFieldType.Segmented,
options: [
{ label: 'Everything', value: 'everything' },
{ label: 'Space', value: 'space' },
{ label: 'Page', value: 'page' },
],
},
{
name: 'config.page_id',
label: 'Page ID',
type: FormFieldType.Text,
customValidate: (val: string, formValues: any) => {
const index_mode = formValues?.config?.index_mode;
console.log('index_mode', index_mode, val);
if (!val && index_mode === 'page') {
return 'Page ID is required';
}
return true;
},
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
return index_mode === 'page';
},
},
{
name: 'config.space',
label: 'Space Key',
type: FormFieldType.Text,
customValidate: (val: string, formValues: any) => {
const index_mode = formValues?.config?.index_mode;
if (!val && index_mode === 'space') {
return 'Space Key is required';
}
return true;
},
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
return index_mode === 'space';
},
},
{
name: 'config.index_recursively',
label: 'Index Recursively',
type: FormFieldType.Checkbox,
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
return index_mode === 'page';
},
},
{
name: FilterFormField + '.tip',
label: ' ',
type: FormFieldType.Custom,
shouldRender: (formValues: any) => {
const index_mode = formValues?.config?.index_mode;
return index_mode === 'everything';
},
render: () => (
<div className="text-sm text-text-secondary bg-bg-card border border-border-button rounded-md px-3 py-2">
{
'This choice will index all pages the provided credentials have access to.'
}
</div>
),
},
{
label: 'Space Key',
name: 'config.space',
type: FormFieldType.Text,
required: false,
hidden: true,
},
{
label: 'Page ID',
name: 'config.page_id',
type: FormFieldType.Text,
required: false,
hidden: true,
},
{
label: 'Index Recursively',
name: 'config.index_recursively',
type: FormFieldType.Checkbox,
required: false,
hidden: true,
},
];

View File

@ -4,11 +4,13 @@ import { t, TFunction } from 'i18next';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import BoxTokenField from '../component/box-token-field';
import { ConfluenceIndexingModeField } from '../component/confluence-token-field';
import GmailTokenField from '../component/gmail-token-field';
import GoogleDriveTokenField from '../component/google-drive-token-field';
import { IDataSourceInfoMap } from '../interface';
import { bitbucketConstant } from './bitbucket-constant';
import { confluenceConstant } from './confluence-constant';
import { S3Constant } from './s3-constant';
export enum DataSourceKey {
CONFLUENCE = 'confluence',
S3 = 's3',
@ -29,6 +31,7 @@ export enum DataSourceKey {
ASANA = 'asana',
IMAP = 'imap',
GITHUB = 'github',
BITBUCKET = 'bitbucket',
ZENDESK = 'zendesk',
// SHAREPOINT = 'sharepoint',
// SLACK = 'slack',
@ -134,6 +137,11 @@ export const generateDataSourceInfo = (t: TFunction) => {
description: t(`setting.${DataSourceKey.IMAP}Description`),
icon: <SvgIcon name={'data-source/imap'} width={38} />,
},
[DataSourceKey.BITBUCKET]: {
name: 'Bitbucket',
description: t(`setting.${DataSourceKey.BITBUCKET}Description`),
icon: <SvgIcon name={'data-source/bitbucket'} width={38} />,
},
[DataSourceKey.ZENDESK]: {
name: 'Zendesk',
description: t(`setting.${DataSourceKey.ZENDESK}Description`),
@ -294,67 +302,7 @@ export const DataSourceFormFields = {
},
],
[DataSourceKey.CONFLUENCE]: [
{
label: 'Confluence Username',
name: 'config.credentials.confluence_username',
type: FormFieldType.Text,
required: true,
tooltip: 'A descriptive name for the connector.',
},
{
label: 'Confluence Access Token',
name: 'config.credentials.confluence_access_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Wiki Base URL',
name: 'config.wiki_base',
type: FormFieldType.Text,
required: false,
tooltip: t('setting.confluenceWikiBaseUrlTip'),
},
{
label: 'Is Cloud',
name: 'config.is_cloud',
type: FormFieldType.Checkbox,
required: false,
tooltip: t('setting.confluenceIsCloudTip'),
},
{
label: 'Index Method',
name: 'config.index_mode',
type: FormFieldType.Text,
required: false,
horizontal: true,
labelClassName: 'self-start pt-4',
render: (fieldProps: any) => (
<ConfluenceIndexingModeField {...fieldProps} />
),
},
{
label: 'Space Key',
name: 'config.space',
type: FormFieldType.Text,
required: false,
hidden: true,
},
{
label: 'Page ID',
name: 'config.page_id',
type: FormFieldType.Text,
required: false,
hidden: true,
},
{
label: 'Index Recursively',
name: 'config.index_recursively',
type: FormFieldType.Checkbox,
required: false,
hidden: true,
},
],
[DataSourceKey.CONFLUENCE]: confluenceConstant(t),
[DataSourceKey.GOOGLE_DRIVE]: [
{
label: 'Primary Admin Email',
@ -828,6 +776,7 @@ export const DataSourceFormFields = {
required: false,
},
],
[DataSourceKey.BITBUCKET]: bitbucketConstant(t),
[DataSourceKey.ZENDESK]: [
{
label: 'Zendesk Domain',
@ -919,6 +868,7 @@ export const DataSourceFormDefaultValues = {
wiki_base: '',
is_cloud: true,
space: '',
page_id: '',
credentials: {
confluence_username: '',
confluence_access_token: '',
@ -1112,6 +1062,19 @@ export const DataSourceFormDefaultValues = {
},
},
},
[DataSourceKey.BITBUCKET]: {
name: '',
source: DataSourceKey.BITBUCKET,
config: {
workspace: '',
index_mode: 'workspace',
repository_slugs: '',
projects: '',
},
credentials: {
bitbucket_api_token: '',
},
},
[DataSourceKey.ZENDESK]: {
name: '',
source: DataSourceKey.ZENDESK,