Files
ragflow/web/src/pages/user-setting/data-source/constant/index.tsx
web-dev0521 cd18cfab79 feat(connector): implement Outlook data source connector (issue #15332) (#15333)
### What problem does this PR solve?

Closes #15332.

RAGFlow can index Gmail and generic IMAP mailboxes but had no native
connector for Outlook / Microsoft 365 mail. Organisations on Microsoft
365 had no way to bring mailbox content into a knowledge base through
Microsoft Graph.

This PR adds a net-new Outlook data source that:

- Authenticates against Microsoft Graph with the same MSAL
client-credentials flow already used by the SharePoint and Teams
  connectors (no new auth primitives).
- Pages over `/users/{id}/mailFolders/{folder}/messages/delta` per
mailbox and persists `@odata.deltaLink` values in
`OutlookCheckpoint.delta_links`, so incremental syncs only fetch changed
messages.
- Supports two scoping modes:
- **Tenant-wide** (default): enumerates every user in the tenant via
`/users` and syncs each mailbox. Requires `User.Read.All`.
- **Targeted**: when `user_ids` is provided (comma-separated UPNs or
object IDs), only those mailboxes are synced. `User.Read.All` is not
needed in this mode.
- Lets the caller pick the mail folder (`inbox`, `sentitems`, `archive`,
...). Defaults to `inbox`.
- Maps each message to a `Document` shaped after the Gmail connector:
one `TextSection` carrying `From/To/Cc/Subject` headers + body, with
HTML bodies stripped to text inline (no extra dependency).
- Surfaces typed errors on the validation probe:
401 → `ConnectorMissingCredentialError`, 403 →
`InsufficientPermissionsError` (with `Mail.Read` / `User.Read.All`
hint), 404 on a configured mailbox → `ConnectorValidationError`, 5xx →
`UnexpectedValidationError`.
- Skips messages flagged `@removed` by the delta semantics and messages
whose `receivedDateTime` is older than `poll_range_start`.

#### Files

| File | Change |
|------|--------|
| `common/data_source/outlook_connector.py` | **New** —
`OutlookConnector` (`CheckpointedConnectorWithPermSync` +
`SlimConnectorWithPermSync`) + `OutlookCheckpoint` + tiny `_strip_html`
helper. |
| `common/data_source/config.py` | `DocumentSource.OUTLOOK = "outlook"`.
|
| `common/constants.py` | `FileSource.OUTLOOK = "outlook"`. |
| `common/data_source/__init__.py` | Export `OutlookConnector`. |
| `rag/svr/sync_data_source.py` | `Outlook(SyncBase)` with `batch_size`
normalisation, CSV/list parsing of `user_ids`; registered in
`func_factory`. |
| `web/src/pages/user-setting/data-source/constant/index.tsx` |
`DataSourceKey.OUTLOOK`, visibility map (`syncDeletedFiles: true`), info
entry, form fields (tenant_id, client_id, client_secret, folder,
user_ids, batch_size), default values. |
| `web/src/locales/en.ts`, `web/src/locales/zh.ts` |
`outlookDescription` + 5 tooltip keys (EN + ZH). |
| `test/unit_test/data_source/test_outlook_connector_unit.py` | **New**
— 19 unit tests (`p1`/`p2`/`p3`) covering auth, validation (tenant-wide
vs specific user vs error paths), checkpoint helpers, user enumeration
pagination, message filtering, HTML body stripping. |

#### Required Azure AD permissions

- `Mail.Read` (Application, admin-granted) — always.
- `User.Read.All` (Application, admin-granted) — only when `user_ids` is
left blank so the connector can enumerate mailboxes.

#### Out of scope

- **Attachment indexing.** The current connector emits message body +
headers; binary attachments are flagged via `metadata.has_attachments`
but not pulled. Adding attachment hydration is straightforward but
scoped out per the issue's "decide whether attachments are indexed in
the first version" note.
- **Delegated (per-user) OAuth.** The connector uses app-only
credentials, consistent with the SharePoint / Teams precedent in this
codebase.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2026-05-29 21:52:29 +08:00

2056 lines
52 KiB
TypeScript

import { FormFieldConfig, FormFieldType } from '@/components/dynamic-form';
import { IconFontFill } from '@/components/icon-font';
import SvgIcon from '@/components/svg-icon';
import { t, TFunction } from 'i18next';
import { Mail, Rss } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import BoxTokenField from '../component/box-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 { jiraConstant } from './jira-constant';
import { S3Constant } from './s3-constant';
import { seafileConstant } from './seafile-constant';
export enum DataSourceKey {
CONFLUENCE = 'confluence',
NOTION = 'notion',
GOOGLE_DRIVE = 'google_drive',
GMAIL = 'gmail',
GOOGLE_CLOUD_STORAGE = 'google_cloud_storage',
OCI_STORAGE = 'oci_storage',
S3 = 's3',
R2 = 'r2',
JIRA = 'jira',
BOX = 'box',
DROPBOX = 'dropbox',
BITBUCKET = 'bitbucket',
GITLAB = 'gitlab',
GITHUB = 'github',
MOODLE = 'moodle',
DISCORD = 'discord',
ZENDESK = 'zendesk',
WEBDAV = 'webdav',
AIRTABLE = 'airtable',
ASANA = 'asana',
IMAP = 'imap',
DINGTALK_AI_TABLE = 'dingtalk_ai_table',
SEAFILE = 'seafile',
MYSQL = 'mysql',
POSTGRESQL = 'postgresql',
REST_API = 'rest_api',
RSS = 'rss',
ONEDRIVE = 'onedrive',
OUTLOOK = 'outlook',
TEAMS = 'teams',
SLACK = 'slack',
SHAREPOINT = 'sharepoint',
}
type DataSourceFeatureVisibility = {
syncDeletedFiles?: boolean;
};
type DataSourceFormValues = Record<string, any>;
export const DataSourceFeatureVisibilityMap: Partial<
Record<DataSourceKey, DataSourceFeatureVisibility>
> = {
[DataSourceKey.GITHUB]: {
syncDeletedFiles: true,
},
[DataSourceKey.GITLAB]: {
syncDeletedFiles: true,
},
[DataSourceKey.GOOGLE_DRIVE]: {
syncDeletedFiles: true,
},
[DataSourceKey.GMAIL]: {
syncDeletedFiles: true,
},
[DataSourceKey.IMAP]: {
syncDeletedFiles: true,
},
[DataSourceKey.CONFLUENCE]: {
syncDeletedFiles: true,
},
[DataSourceKey.BOX]: {
syncDeletedFiles: true,
},
[DataSourceKey.DROPBOX]: {
syncDeletedFiles: true,
},
[DataSourceKey.S3]: {
syncDeletedFiles: true,
},
[DataSourceKey.R2]: {
syncDeletedFiles: true,
},
[DataSourceKey.GOOGLE_CLOUD_STORAGE]: {
syncDeletedFiles: true,
},
[DataSourceKey.OCI_STORAGE]: {
syncDeletedFiles: true,
},
[DataSourceKey.NOTION]: {
syncDeletedFiles: true,
},
[DataSourceKey.DISCORD]: {
syncDeletedFiles: true,
},
[DataSourceKey.JIRA]: {
syncDeletedFiles: true,
},
[DataSourceKey.BITBUCKET]: {
syncDeletedFiles: true,
},
[DataSourceKey.AIRTABLE]: {
syncDeletedFiles: true,
},
[DataSourceKey.DINGTALK_AI_TABLE]: {
syncDeletedFiles: true,
},
[DataSourceKey.WEBDAV]: {
syncDeletedFiles: true,
},
[DataSourceKey.ZENDESK]: {
syncDeletedFiles: true,
},
[DataSourceKey.SEAFILE]: {
syncDeletedFiles: true,
},
[DataSourceKey.ASANA]: {
syncDeletedFiles: true,
},
[DataSourceKey.RSS]: {
syncDeletedFiles: true,
},
[DataSourceKey.MOODLE]: {
syncDeletedFiles: true,
},
[DataSourceKey.ONEDRIVE]: {
syncDeletedFiles: true,
},
[DataSourceKey.OUTLOOK]: {
syncDeletedFiles: true,
},
[DataSourceKey.TEAMS]: {
syncDeletedFiles: true,
},
[DataSourceKey.SLACK]: {
syncDeletedFiles: true,
},
[DataSourceKey.SHAREPOINT]: {
syncDeletedFiles: true,
},
[DataSourceKey.MYSQL]: {
syncDeletedFiles: true,
},
[DataSourceKey.POSTGRESQL]: {
syncDeletedFiles: true,
},
};
const isDataSourceFeatureVisible = (
source?: DataSourceKey,
feature?: keyof DataSourceFeatureVisibility,
) => {
if (!source || !feature) {
return false;
}
return Boolean(DataSourceFeatureVisibilityMap[source]?.[feature]);
};
export const generateDataSourceInfo = (t: TFunction) => {
return {
[DataSourceKey.RSS]: {
name: 'RSS',
description: t(`setting.${DataSourceKey.RSS}Description`),
icon: <Rss className="text-text-primary" size={22} />,
},
[DataSourceKey.GOOGLE_CLOUD_STORAGE]: {
name: 'Google Cloud Storage',
description: t(
`setting.${DataSourceKey.GOOGLE_CLOUD_STORAGE}Description`,
),
icon: <SvgIcon name={'data-source/google-cloud-storage'} width={38} />,
},
[DataSourceKey.OCI_STORAGE]: {
name: 'Oracle Storage',
description: t(`setting.${DataSourceKey.OCI_STORAGE}Description`),
icon: <SvgIcon name={'data-source/oracle-storage'} width={38} />,
},
[DataSourceKey.R2]: {
name: 'R2',
description: t(`setting.${DataSourceKey.R2}Description`),
icon: <SvgIcon name={'data-source/r2'} width={38} />,
},
[DataSourceKey.S3]: {
name: 'S3',
description: t(`setting.${DataSourceKey.S3}Description`),
icon: <SvgIcon name={'data-source/s3'} width={38} />,
},
[DataSourceKey.NOTION]: {
name: 'Notion',
description: t(`setting.${DataSourceKey.NOTION}Description`),
icon: <SvgIcon name={'data-source/notion'} width={38} />,
},
[DataSourceKey.DISCORD]: {
name: 'Discord',
description: t(`setting.${DataSourceKey.DISCORD}Description`),
icon: <SvgIcon name={'data-source/discord'} width={38} />,
},
[DataSourceKey.CONFLUENCE]: {
name: 'Confluence',
description: t(`setting.${DataSourceKey.CONFLUENCE}Description`),
icon: <SvgIcon name={'data-source/confluence'} width={38} />,
},
[DataSourceKey.GOOGLE_DRIVE]: {
name: 'Google Drive',
description: t(`setting.${DataSourceKey.GOOGLE_DRIVE}Description`),
icon: <SvgIcon name={'data-source/google-drive'} width={38} />,
},
[DataSourceKey.GMAIL]: {
name: 'Gmail',
description: t(`setting.${DataSourceKey.GMAIL}Description`),
icon: <SvgIcon name={'data-source/gmail'} width={38} />,
},
[DataSourceKey.REST_API]: {
name: 'REST API',
description: t(`setting.${DataSourceKey.REST_API}Description`),
icon: <SvgIcon name={'data-source/rest-api'} width={38} />,
},
[DataSourceKey.MOODLE]: {
name: 'Moodle',
description: t(`setting.${DataSourceKey.MOODLE}Description`),
icon: <SvgIcon name={'data-source/moodle'} width={38} />,
},
[DataSourceKey.TEAMS]: {
name: 'Microsoft Teams',
description: t(`setting.${DataSourceKey.TEAMS}Description`),
icon: <SvgIcon name={'data-source/teams'} width={38} />,
},
[DataSourceKey.SLACK]: {
name: 'Slack',
description: t(`setting.${DataSourceKey.SLACK}Description`),
icon: <SvgIcon name={'data-source/slack'} width={38} />,
},
[DataSourceKey.SHAREPOINT]: {
name: 'SharePoint',
description: t(`setting.${DataSourceKey.SHAREPOINT}Description`),
icon: <SvgIcon name={'data-source/sharepoint'} width={38} />,
},
[DataSourceKey.JIRA]: {
name: 'Jira',
description: t(`setting.${DataSourceKey.JIRA}Description`),
icon: <SvgIcon name={'data-source/jira'} width={38} />,
},
[DataSourceKey.WEBDAV]: {
name: 'WebDAV',
description: t(`setting.${DataSourceKey.WEBDAV}Description`),
icon: <SvgIcon name={'data-source/webdav'} width={38} />,
},
[DataSourceKey.DROPBOX]: {
name: 'Dropbox',
description: t(`setting.${DataSourceKey.DROPBOX}Description`),
icon: <SvgIcon name={'data-source/dropbox'} width={38} />,
},
[DataSourceKey.BOX]: {
name: 'Box',
description: t(`setting.${DataSourceKey.BOX}Description`),
icon: <SvgIcon name={'data-source/box'} width={38} />,
},
[DataSourceKey.AIRTABLE]: {
name: 'Airtable',
description: t(`setting.${DataSourceKey.AIRTABLE}Description`),
icon: <SvgIcon name={'data-source/airtable'} width={38} />,
},
[DataSourceKey.DINGTALK_AI_TABLE]: {
name: 'Dingtalk AI Table',
description: t(`setting.dingtalkAITableDescription`),
icon: <SvgIcon name={'data-source/dingtalk-ai-table'} width={38} />,
},
[DataSourceKey.GITLAB]: {
name: 'GitLab',
description: t(`setting.${DataSourceKey.GITLAB}Description`),
icon: <SvgIcon name={'data-source/gitlab'} width={38} />,
},
[DataSourceKey.ASANA]: {
name: 'Asana',
description: t(`setting.${DataSourceKey.ASANA}Description`),
icon: <SvgIcon name={'data-source/asana'} width={38} />,
},
[DataSourceKey.GITHUB]: {
name: 'GitHub',
description: t(`setting.${DataSourceKey.GITHUB}Description`),
icon: (
<IconFontFill
// name="a-DiscordIconSVGVectorIcon"
name="GitHub"
className="text-text-primary size-6"
></IconFontFill>
),
},
[DataSourceKey.IMAP]: {
name: 'IMAP',
description: t(`setting.${DataSourceKey.IMAP}Description`),
icon: <Mail className="text-text-primary" size={22} />,
},
[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`),
icon: <SvgIcon name={'data-source/zendesk'} width={38} />,
},
[DataSourceKey.SEAFILE]: {
name: 'SeaFile',
description: t(`setting.${DataSourceKey.SEAFILE}Description`),
icon: <SvgIcon name={'data-source/seafile'} width={38} />,
},
[DataSourceKey.MYSQL]: {
name: 'MySQL',
description: t(`setting.${DataSourceKey.MYSQL}Description`),
icon: <SvgIcon name={'data-source/mysql'} width={38} />,
},
[DataSourceKey.POSTGRESQL]: {
name: 'PostgreSQL',
description: t(`setting.${DataSourceKey.POSTGRESQL}Description`),
icon: <SvgIcon name={'data-source/postgresql'} width={38} />,
},
[DataSourceKey.ONEDRIVE]: {
name: 'OneDrive',
description: t(`setting.${DataSourceKey.ONEDRIVE}Description`),
icon: <SvgIcon name={'data-source/onedrive'} width={38} />,
},
[DataSourceKey.OUTLOOK]: {
name: 'Outlook',
description: t(`setting.${DataSourceKey.OUTLOOK}Description`),
icon: <Mail className="text-text-primary" size={22} />,
},
};
};
export const useDataSourceInfo = () => {
const { t } = useTranslation();
const [dataSourceInfo, setDataSourceInfo] = useState<IDataSourceInfoMap>(
generateDataSourceInfo(t) as IDataSourceInfoMap,
);
useEffect(() => {
setDataSourceInfo(generateDataSourceInfo(t));
}, [t]);
return { dataSourceInfo };
};
const isPlainObject = (value: unknown): value is DataSourceFormValues =>
typeof value === 'object' && value !== null && !Array.isArray(value);
export const mergeDataSourceFormValues = (
...values: Array<DataSourceFormValues | undefined>
): DataSourceFormValues =>
values.reduce<DataSourceFormValues>((result, current) => {
if (!current) {
return result;
}
const next = { ...result };
Object.entries(current).forEach(([key, value]) => {
if (isPlainObject(value) && isPlainObject(next[key])) {
next[key] = mergeDataSourceFormValues(next[key], value);
} else {
next[key] = value;
}
});
return next;
}, {});
export const DataSourceFormBaseFields = [
{
id: 'Id',
name: 'id',
type: FormFieldType.Text,
required: false,
hidden: true,
},
{
label: 'Name',
name: 'name',
type: FormFieldType.Text,
required: true,
tooltip: t('setting.connectorNameTip'),
},
{
label: 'Source',
name: 'source',
type: FormFieldType.Select,
required: true,
hidden: true,
options: Object.keys(DataSourceKey).map((item) => ({
label: item,
value: DataSourceKey[item as keyof typeof DataSourceKey],
})),
},
];
export const getCommonExtraFields = (
source?: DataSourceKey,
): FormFieldConfig[] => [
{
label: t('setting.syncDeletedFiles'),
name: 'config.sync_deleted_files',
type: FormFieldType.Checkbox,
required: false,
defaultValue: false,
shouldRender: () => isDataSourceFeatureVisible(source, 'syncDeletedFiles'),
},
];
export const getCommonExtraDefaultValues = () => ({
config: {
sync_deleted_files: false,
},
});
export const DataSourceFormFields = {
[DataSourceKey.ONEDRIVE]: [
{
label: 'Tenant ID',
name: 'config.credentials.tenant_id',
type: FormFieldType.Text,
required: true,
placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
tooltip: t('setting.onedriveTenantIdTip'),
},
{
label: 'Client ID',
name: 'config.credentials.client_id',
type: FormFieldType.Text,
required: true,
placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
tooltip: t('setting.onedriveClientIdTip'),
},
{
label: 'Client Secret',
name: 'config.credentials.client_secret',
type: FormFieldType.Password,
required: true,
tooltip: t('setting.onedriveClientSecretTip'),
},
{
label: 'Folder Path (optional)',
name: 'config.folder_path',
type: FormFieldType.Text,
required: false,
placeholder: '/Documents/Reports',
tooltip: t('setting.onedriveFolderPathTip'),
},
{
label: 'Batch Size',
name: 'config.batch_size',
type: FormFieldType.Number,
required: false,
validation: {
min: 1,
message: 'Batch Size must be at least 1',
},
},
],
[DataSourceKey.OUTLOOK]: [
{
label: 'Tenant ID',
name: 'config.credentials.tenant_id',
type: FormFieldType.Text,
required: true,
placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
tooltip: t('setting.outlookTenantIdTip'),
},
{
label: 'Client ID',
name: 'config.credentials.client_id',
type: FormFieldType.Text,
required: true,
placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
tooltip: t('setting.outlookClientIdTip'),
},
{
label: 'Client Secret',
name: 'config.credentials.client_secret',
type: FormFieldType.Password,
required: true,
tooltip: t('setting.outlookClientSecretTip'),
},
{
label: 'Mail Folder',
name: 'config.folder',
type: FormFieldType.Text,
required: false,
placeholder: 'inbox',
tooltip: t('setting.outlookFolderTip'),
},
{
label: 'Mailbox User IDs (optional)',
name: 'config.user_ids',
type: FormFieldType.Text,
required: false,
placeholder: 'support@example.com, sales@example.com',
tooltip: t('setting.outlookUserIdsTip'),
},
{
label: 'Batch Size',
name: 'config.batch_size',
type: FormFieldType.Number,
required: false,
validation: {
min: 1,
message: 'Batch Size must be at least 1',
},
},
],
[DataSourceKey.RSS]: [
{
label: 'Feed URL',
name: 'config.feed_url',
type: FormFieldType.Text,
required: true,
placeholder: 'https://example.com/feed.xml',
},
{
label: 'Batch Size',
name: 'config.batch_size',
type: FormFieldType.Number,
required: false,
validation: {
min: 1,
message: 'Batch Size must be at least 1',
},
},
],
[DataSourceKey.GOOGLE_CLOUD_STORAGE]: [
{
label: 'GCS Access Key ID',
name: 'config.credentials.access_key_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'GCS Secret Access Key',
name: 'config.credentials.secret_access_key',
type: FormFieldType.Password,
required: true,
},
{
label: 'Bucket Name',
name: 'config.bucket_name',
type: FormFieldType.Text,
required: true,
},
],
[DataSourceKey.OCI_STORAGE]: [
{
label: 'OCI Namespace',
name: 'config.credentials.namespace',
type: FormFieldType.Text,
required: true,
},
{
label: 'OCI Region',
name: 'config.credentials.region',
type: FormFieldType.Text,
required: true,
},
{
label: 'OCI Access Key ID',
name: 'config.credentials.access_key_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'OCI Secret Access Key',
name: 'config.credentials.secret_access_key',
type: FormFieldType.Password,
required: true,
},
{
label: 'Bucket Name',
name: 'config.bucket_name',
type: FormFieldType.Text,
required: true,
},
],
[DataSourceKey.R2]: [
{
label: 'R2 Account ID',
name: 'config.credentials.account_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'R2 Access Key ID',
name: 'config.credentials.r2_access_key_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'R2 Secret Access Key',
name: 'config.credentials.r2_secret_access_key',
type: FormFieldType.Password,
required: true,
},
{
label: 'Bucket Name',
name: 'config.bucket_name',
type: FormFieldType.Text,
required: true,
},
],
[DataSourceKey.S3]: S3Constant(t),
[DataSourceKey.NOTION]: [
{
label: 'Notion Integration Token',
name: 'config.credentials.notion_integration_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Root Page Id',
name: 'config.root_page_id',
type: FormFieldType.Text,
required: false,
},
],
[DataSourceKey.DISCORD]: [
{
label: 'Discord Bot Token',
name: 'config.credentials.discord_bot_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Server IDs',
name: 'config.server_ids',
type: FormFieldType.Tag,
required: false,
},
{
label: 'Channels',
name: 'config.channels',
type: FormFieldType.Tag,
required: false,
},
],
[DataSourceKey.CONFLUENCE]: confluenceConstant(t),
[DataSourceKey.GOOGLE_DRIVE]: [
{
label: 'Primary Admin Email',
name: 'config.credentials.google_primary_admin',
type: FormFieldType.Text,
required: true,
placeholder: 'admin@example.com',
tooltip: t('setting.google_drivePrimaryAdminTip'),
},
{
label: 'OAuth Token JSON',
name: 'config.credentials.google_tokens',
type: FormFieldType.Textarea,
required: true,
render: (fieldProps: any) => (
<GoogleDriveTokenField
value={fieldProps.value}
onChange={fieldProps.onChange}
/>
),
tooltip: t('setting.google_driveTokenTip'),
},
{
label: 'My Drive Emails',
name: 'config.my_drive_emails',
type: FormFieldType.Text,
required: true,
placeholder: 'user1@example.com,user2@example.com',
tooltip: t('setting.google_driveMyDriveEmailsTip'),
},
{
label: 'Shared Folder URLs',
name: 'config.shared_folder_urls',
type: FormFieldType.Textarea,
required: true,
placeholder:
'https://drive.google.com/drive/folders/XXXXX,https://drive.google.com/drive/folders/YYYYY',
tooltip: t('setting.google_driveSharedFoldersTip'),
},
// The fields below are intentionally disabled for now. Uncomment them when we
// reintroduce shared drive controls or advanced impersonation options.
// {
// label: 'Shared Drive URLs',
// name: 'config.shared_drive_urls',
// type: FormFieldType.Text,
// required: false,
// placeholder:
// 'Optional: comma-separated shared drive links if you want to include them.',
// },
// {
// label: 'Specific User Emails',
// name: 'config.specific_user_emails',
// type: FormFieldType.Text,
// required: false,
// placeholder:
// 'Optional: comma-separated list of users to impersonate (overrides defaults).',
// },
// {
// label: 'Include My Drive',
// name: 'config.include_my_drives',
// type: FormFieldType.Checkbox,
// required: false,
// defaultValue: true,
// },
// {
// label: 'Include Shared Drives',
// name: 'config.include_shared_drives',
// type: FormFieldType.Checkbox,
// required: false,
// defaultValue: false,
// },
// {
// label: 'Include “Shared with me”',
// name: 'config.include_files_shared_with_me',
// type: FormFieldType.Checkbox,
// required: false,
// defaultValue: false,
// },
// {
// label: 'Allow Images',
// name: 'config.allow_images',
// type: FormFieldType.Checkbox,
// required: false,
// defaultValue: false,
// },
{
label: '',
name: 'config.credentials.authentication_method',
type: FormFieldType.Text,
required: false,
hidden: true,
defaultValue: 'uploaded',
},
],
[DataSourceKey.GMAIL]: [
{
label: 'Primary Admin Email',
name: 'config.credentials.google_primary_admin',
type: FormFieldType.Text,
required: true,
placeholder: 'admin@example.com',
tooltip: t('setting.gmailPrimaryAdminTip'),
},
{
label: 'OAuth Token JSON',
name: 'config.credentials.google_tokens',
type: FormFieldType.Textarea,
required: true,
render: (fieldProps: any) => (
<GmailTokenField
value={fieldProps.value}
onChange={fieldProps.onChange}
/>
),
tooltip: t('setting.gmailTokenTip'),
},
{
label: '',
name: 'config.credentials.authentication_method',
type: FormFieldType.Text,
required: false,
hidden: true,
defaultValue: 'uploaded',
},
],
[DataSourceKey.MOODLE]: [
{
label: 'Moodle URL',
name: 'config.moodle_url',
type: FormFieldType.Text,
required: true,
placeholder: 'https://moodle.example.com',
},
{
label: 'API Token',
name: 'config.credentials.moodle_token',
type: FormFieldType.Password,
required: true,
},
],
[DataSourceKey.TEAMS]: [
{
label: 'Tenant ID',
name: 'config.credentials.tenant_id',
type: FormFieldType.Text,
required: true,
tooltip: t('setting.teamsTenantIdTip'),
},
{
label: 'Client ID',
name: 'config.credentials.client_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'Client Secret',
name: 'config.credentials.client_secret',
type: FormFieldType.Password,
required: true,
},
],
[DataSourceKey.SLACK]: [
{
label: 'Slack Bot Token',
name: 'config.credentials.slack_bot_token',
type: FormFieldType.Password,
required: true,
tooltip: t('setting.slackBotTokenTip'),
},
{
label: 'Channels',
name: 'config.channels',
type: FormFieldType.Tag,
required: false,
tooltip: t('setting.slackChannelsTip'),
},
],
[DataSourceKey.SHAREPOINT]: [
{
label: 'Site URL',
name: 'config.credentials.site_url',
type: FormFieldType.Text,
required: true,
placeholder: 'https://contoso.sharepoint.com/sites/MySite',
tooltip: t('setting.sharepointSiteUrlTip'),
},
{
label: 'Tenant ID',
name: 'config.credentials.tenant_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'Client ID',
name: 'config.credentials.client_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'Client Secret',
name: 'config.credentials.client_secret',
type: FormFieldType.Password,
required: true,
},
],
[DataSourceKey.JIRA]: jiraConstant(t),
[DataSourceKey.WEBDAV]: [
{
label: 'WebDAV Server URL',
name: 'config.base_url',
type: FormFieldType.Text,
required: true,
placeholder: 'https://webdav.example.com',
},
{
label: 'Username',
name: 'config.credentials.username',
type: FormFieldType.Text,
required: true,
},
{
label: 'Password',
name: 'config.credentials.password',
type: FormFieldType.Password,
required: true,
},
{
label: 'Remote Path',
name: 'config.remote_path',
type: FormFieldType.Text,
required: false,
placeholder: '/',
tooltip: t('setting.webdavRemotePathTip'),
},
],
[DataSourceKey.DROPBOX]: [
{
label: 'Access Token',
name: 'config.credentials.dropbox_access_token',
type: FormFieldType.Password,
required: true,
tooltip: t('setting.dropboxAccessTokenTip'),
},
{
label: 'Batch Size',
name: 'config.batch_size',
type: FormFieldType.Number,
required: false,
placeholder: 'Defaults to 2',
},
],
[DataSourceKey.BOX]: [
{
label: 'Box OAuth JSON',
name: 'config.credentials.box_tokens',
type: FormFieldType.Textarea,
required: true,
render: (fieldProps: any) => (
<BoxTokenField
value={fieldProps.value}
onChange={fieldProps.onChange}
placeholder='{ "client_id": "...", "client_secret": "...", "redirect_uri": "..." }'
/>
),
},
{
label: 'Folder ID',
name: 'config.folder_id',
type: FormFieldType.Text,
required: false,
placeholder: 'Defaults root',
},
],
[DataSourceKey.AIRTABLE]: [
{
label: 'Access Token',
name: 'config.credentials.airtable_access_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Base ID',
name: 'config.base_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'Table Name OR ID',
name: 'config.table_name_or_id',
type: FormFieldType.Text,
required: true,
},
],
[DataSourceKey.DINGTALK_AI_TABLE]: [
{
label: 'Access Token',
name: 'config.credentials.access_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Base ID',
name: 'config.table_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'Operator ID',
name: 'config.operator_id',
type: FormFieldType.Text,
required: true,
},
],
[DataSourceKey.GITLAB]: [
{
label: 'Project Owner',
name: 'config.project_owner',
type: FormFieldType.Text,
required: true,
},
{
label: 'Project Name',
name: 'config.project_name',
type: FormFieldType.Text,
required: true,
},
{
label: 'GitLab Personal Access Token',
name: 'config.credentials.gitlab_access_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'GitLab URL',
name: 'config.gitlab_url',
type: FormFieldType.Text,
required: true,
placeholder: 'https://gitlab.com',
},
{
label: 'include Merge Requests',
name: 'config.include_mrs',
type: FormFieldType.Checkbox,
required: false,
defaultValue: true,
},
{
label: 'include Issues',
name: 'config.include_issues',
type: FormFieldType.Checkbox,
required: false,
defaultValue: true,
},
{
label: 'include Code Files',
name: 'config.include_code_files',
type: FormFieldType.Checkbox,
required: false,
defaultValue: true,
},
],
[DataSourceKey.ASANA]: [
{
label: 'API Token',
name: 'config.credentials.asana_api_token_secret',
type: FormFieldType.Password,
required: true,
},
{
label: 'Workspace ID',
name: 'config.asana_workspace_id',
type: FormFieldType.Text,
required: true,
},
{
label: 'Project IDs',
name: 'config.asana_project_ids',
type: FormFieldType.Text,
required: false,
},
{
label: 'Team ID',
name: 'config.asana_team_id',
type: FormFieldType.Text,
required: false,
},
],
[DataSourceKey.GITHUB]: [
{
label: 'Repository Owner',
name: 'config.repository_owner',
type: FormFieldType.Text,
required: true,
},
{
label: 'Repository Name',
name: 'config.repository_name',
type: FormFieldType.Text,
required: true,
},
{
label: 'GitHub Access Token',
name: 'config.credentials.github_access_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Include Pull Requests',
name: 'config.include_pull_requests',
type: FormFieldType.Checkbox,
required: false,
defaultValue: true,
},
{
label: 'Include Issues',
name: 'config.include_issues',
type: FormFieldType.Checkbox,
required: false,
defaultValue: true,
},
],
[DataSourceKey.IMAP]: [
{
label: 'Username',
name: 'config.credentials.imap_username',
type: FormFieldType.Text,
required: true,
},
{
label: 'Password',
name: 'config.credentials.imap_password',
type: FormFieldType.Password,
required: true,
},
{
label: 'Host',
name: 'config.imap_host',
type: FormFieldType.Text,
required: true,
},
{
label: 'Port',
name: 'config.imap_port',
type: FormFieldType.Number,
required: true,
},
{
label: 'Mailboxes',
name: 'config.imap_mailbox',
type: FormFieldType.Tag,
required: false,
},
{
label: 'Poll Range',
name: 'config.poll_range',
type: FormFieldType.Number,
required: false,
},
],
[DataSourceKey.BITBUCKET]: bitbucketConstant(t),
[DataSourceKey.ZENDESK]: [
{
label: 'Zendesk Domain',
name: 'config.credentials.zendesk_subdomain',
type: FormFieldType.Text,
required: true,
},
{
label: 'Zendesk Email',
name: 'config.credentials.zendesk_email',
type: FormFieldType.Text,
required: true,
},
{
label: 'Zendesk Token',
name: 'config.credentials.zendesk_token',
type: FormFieldType.Password,
required: true,
},
{
label: 'Content',
name: 'config.zendesk_content_type',
type: FormFieldType.Segmented,
required: true,
options: [
{ label: 'Articles', value: 'articles' },
{ label: 'Tickets', value: 'tickets' },
],
},
],
[DataSourceKey.SEAFILE]: seafileConstant(t),
[DataSourceKey.MYSQL]: [
{
label: 'Host',
name: 'config.host',
type: FormFieldType.Text,
required: true,
placeholder: 'localhost',
},
{
label: 'Port',
name: 'config.port',
type: FormFieldType.Number,
required: true,
placeholder: '3306',
},
{
label: 'Database',
name: 'config.database',
type: FormFieldType.Text,
required: true,
},
{
label: 'Username',
name: 'config.credentials.username',
type: FormFieldType.Text,
required: true,
},
{
label: 'Password',
name: 'config.credentials.password',
type: FormFieldType.Password,
required: true,
},
{
label: 'SQL Query',
name: 'config.query',
type: FormFieldType.Textarea,
required: false,
placeholder: 'Leave empty to load all tables',
tooltip: t('setting.mysqlQueryTip'),
},
{
label: 'Content Columns',
name: 'config.content_columns',
type: FormFieldType.Text,
required: false,
placeholder: 'title,description,content',
tooltip: t('setting.mysqlContentColumnsTip'),
},
{
label: 'Metadata Columns',
name: 'config.metadata_columns',
type: FormFieldType.Text,
required: false,
placeholder: 'id,category,status',
tooltip: t('setting.mysqlMetadataColumnsTip'),
},
{
label: 'ID Column',
name: 'config.id_column',
type: FormFieldType.Text,
required: false,
placeholder: 'id',
tooltip: t('setting.mysqlIdColumnTip'),
},
{
label: 'Timestamp Column',
name: 'config.timestamp_column',
type: FormFieldType.Text,
required: false,
placeholder: 'updated_at',
tooltip: t('setting.mysqlTimestampColumnTip'),
},
],
[DataSourceKey.POSTGRESQL]: [
{
label: 'Host',
name: 'config.host',
type: FormFieldType.Text,
required: true,
placeholder: 'localhost',
},
{
label: 'Port',
name: 'config.port',
type: FormFieldType.Number,
required: true,
placeholder: '5432',
},
{
label: 'Database',
name: 'config.database',
type: FormFieldType.Text,
required: true,
},
{
label: 'Username',
name: 'config.credentials.username',
type: FormFieldType.Text,
required: true,
},
{
label: 'Password',
name: 'config.credentials.password',
type: FormFieldType.Password,
required: true,
},
{
label: 'SQL Query',
name: 'config.query',
type: FormFieldType.Textarea,
required: false,
placeholder: 'Leave empty to load all tables',
tooltip: t('setting.postgresqlQueryTip'),
},
{
label: 'Content Columns',
name: 'config.content_columns',
type: FormFieldType.Text,
required: false,
placeholder: 'title,description,content',
tooltip: t('setting.postgresqlContentColumnsTip'),
},
{
label: 'Metadata Columns',
name: 'config.metadata_columns',
type: FormFieldType.Text,
required: false,
placeholder: 'id,category,status',
tooltip: t('setting.postgresqlMetadataColumnsTip'),
},
{
label: 'ID Column',
name: 'config.id_column',
type: FormFieldType.Text,
required: false,
placeholder: 'id',
tooltip: t('setting.postgresqlIdColumnTip'),
},
{
label: 'Timestamp Column',
name: 'config.timestamp_column',
type: FormFieldType.Text,
required: false,
placeholder: 'updated_at',
tooltip: t('setting.postgresqlTimestampColumnTip'),
},
],
[DataSourceKey.REST_API]: [
// ── Essential fields ──────────────────────────────────────────────
{
label: 'Base URL',
name: 'config.url',
type: FormFieldType.Text,
required: true,
placeholder: 'https://api.example.com/v1/resources',
},
{
label: 'HTTP Method',
name: 'config.method',
type: FormFieldType.Select,
required: true,
options: [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
],
defaultValue: 'GET',
},
{
label: 'Query Parameters',
name: 'config.query_params',
type: FormFieldType.Textarea,
required: false,
placeholder: `key=value\none_per_line=true`,
tooltip: t('setting.restApiQueryParamsTip'),
},
{
label: 'Items Path',
name: 'config.items_path',
type: FormFieldType.Text,
required: false,
placeholder: '$.items',
tooltip: t('setting.restApiItemsPathTip'),
},
{
label: 'ID Field',
name: 'config.id_field',
type: FormFieldType.Text,
required: false,
placeholder: 'id',
tooltip: t('setting.restApiIdFieldTip'),
},
{
label: 'Auth Type',
name: 'config.auth_type',
type: FormFieldType.Select,
required: true,
options: [
{ label: 'None', value: 'none' },
{ label: 'API Key (Header)', value: 'api_key_header' },
{ label: 'Bearer Token', value: 'bearer' },
{ label: 'Basic Auth', value: 'basic' },
],
defaultValue: 'none',
},
{
label: 'API Key Header Name',
name: 'config.auth_config.header_name',
type: FormFieldType.Text,
required: false,
placeholder: 'X-API-Key',
shouldRender: (values: any) =>
values?.config?.auth_type === 'api_key_header',
customValidate: (val: string, values: any) => {
if (
values?.config?.auth_type === 'api_key_header' &&
!(val != null && String(val).trim())
) {
return t('setting.restApiValidationApiKeyHeaderNameRequired');
}
return true;
},
},
{
label: 'API Key Value',
name: 'config.credentials.api_key',
type: FormFieldType.Password,
required: false,
shouldRender: (values: any) =>
values?.config?.auth_type === 'api_key_header',
customValidate: (val: string, values: any) => {
if (values?.config?.auth_type === 'api_key_header' && !val) {
return t('setting.restApiValidationApiKeyRequired');
}
return true;
},
},
{
label: 'Bearer Token',
name: 'config.credentials.token',
type: FormFieldType.Password,
required: false,
shouldRender: (values: any) => values?.config?.auth_type === 'bearer',
customValidate: (val: string, values: any) => {
if (values?.config?.auth_type === 'bearer' && !val) {
return t('setting.restApiValidationBearerTokenRequired');
}
return true;
},
},
{
label: 'Username',
name: 'config.credentials.username',
type: FormFieldType.Text,
required: false,
shouldRender: (values: any) => values?.config?.auth_type === 'basic',
customValidate: (val: string, values: any) => {
if (
values?.config?.auth_type === 'basic' &&
!(val != null && String(val).trim())
) {
return t('setting.restApiValidationBasicUsernameRequired');
}
return true;
},
},
{
label: 'Password',
name: 'config.credentials.password',
type: FormFieldType.Password,
required: false,
shouldRender: (values: any) => values?.config?.auth_type === 'basic',
customValidate: (val: string, values: any) => {
if (values?.config?.auth_type === 'basic' && !val) {
return t('setting.restApiValidationBasicPasswordRequired');
}
return true;
},
},
{
label: 'Content Fields',
name: 'config.content_fields',
type: FormFieldType.Text,
required: true,
placeholder: 'title,body',
tooltip: t('setting.restApiContentFieldsTip'),
},
{
label: 'Metadata Fields',
name: 'config.metadata_fields',
type: FormFieldType.Text,
required: false,
placeholder: 'author,category',
tooltip: t('setting.restApiMetadataFieldsTip'),
},
{
label: 'Pagination Type',
name: 'config.pagination_type',
type: FormFieldType.Select,
required: true,
options: [
{ label: 'None', value: 'none' },
{ label: 'Page', value: 'page' },
{ label: 'Offset', value: 'offset' },
{ label: 'Cursor', value: 'cursor' },
],
defaultValue: 'none',
},
{
label: 'Start Page',
name: 'config.pagination_config.start_page',
type: FormFieldType.Number,
required: false,
defaultValue: 1,
shouldRender: (values: any) => values?.config?.pagination_type === 'page',
},
{
label: 'Offset Param',
name: 'config.pagination_config.offset_param',
type: FormFieldType.Text,
required: false,
defaultValue: 'offset',
shouldRender: (values: any) =>
values?.config?.pagination_type === 'offset',
},
{
label: 'Start Offset',
name: 'config.pagination_config.start_offset',
type: FormFieldType.Number,
required: false,
defaultValue: 0,
shouldRender: (values: any) =>
values?.config?.pagination_type === 'offset',
},
{
label: 'Cursor Param',
name: 'config.pagination_config.cursor_param',
type: FormFieldType.Text,
required: false,
defaultValue: 'cursor',
shouldRender: (values: any) =>
values?.config?.pagination_type === 'cursor',
},
{
label: 'Next Cursor JSONPath',
name: 'config.pagination_config.next_cursor_path',
type: FormFieldType.Text,
required: false,
placeholder: '$.next_cursor',
shouldRender: (values: any) =>
values?.config?.pagination_type === 'cursor',
tooltip: t('setting.restApiNextCursorPathTip'),
},
// ── Advanced settings toggle ──────────────────────────────────────
{
label: 'Advanced Settings',
name: 'config.show_advanced',
type: FormFieldType.Switch,
required: false,
defaultValue: false,
},
// ── Advanced fields (hidden until toggled) ────────────────────────
{
label: 'Custom Headers (JSON)',
name: 'config.headers',
type: FormFieldType.Textarea,
required: false,
placeholder: `{"X-Custom-Header": "value"}`,
tooltip: t('setting.restApiHeadersTip'),
shouldRender: (values: any) => !!values?.config?.show_advanced,
},
{
label: 'Limit Param',
name: 'config.pagination_config.limit_param',
type: FormFieldType.Text,
required: false,
placeholder: 'limit (leave empty if already in Query Parameters)',
shouldRender: (values: any) =>
!!values?.config?.show_advanced &&
values?.config?.pagination_type === 'offset',
},
{
label: 'Initial Cursor',
name: 'config.pagination_config.initial_cursor',
type: FormFieldType.Text,
required: false,
shouldRender: (values: any) =>
!!values?.config?.show_advanced &&
values?.config?.pagination_type === 'cursor',
},
{
label: 'Max Pages',
name: 'config.max_pages',
type: FormFieldType.Number,
required: false,
defaultValue: 1000,
shouldRender: (values: any) => !!values?.config?.show_advanced,
},
{
label: 'Request Delay (seconds)',
name: 'config.request_delay',
type: FormFieldType.Number,
required: false,
defaultValue: 0.5,
placeholder: '0.5',
tooltip: t('setting.restApiRequestDelayTip'),
shouldRender: (values: any) => !!values?.config?.show_advanced,
},
{
label: 'Poll Timestamp Field',
name: 'config.poll_timestamp_field',
type: FormFieldType.Text,
required: false,
placeholder: 'updated_at',
tooltip: t('setting.restApiPollTimestampFieldTip'),
shouldRender: (values: any) => !!values?.config?.show_advanced,
},
{
label: 'Request Body (POST) JSON',
name: 'config.request_body',
type: FormFieldType.Textarea,
required: false,
placeholder: `{"status": "published"}`,
tooltip: t('setting.restApiRequestBodyTip'),
shouldRender: (values: any) =>
!!values?.config?.show_advanced && values?.config?.method === 'POST',
},
],
};
export const DataSourceFormDefaultValues = {
[DataSourceKey.RSS]: {
name: '',
source: DataSourceKey.RSS,
config: {
feed_url: '',
batch_size: 2,
},
},
[DataSourceKey.S3]: {
name: '',
source: DataSourceKey.S3,
config: {
bucket_name: '',
bucket_type: 's3',
prefix: '',
credentials: {
aws_access_key_id: '',
aws_secret_access_key: '',
region: '',
authentication_method: 'access_key',
aws_role_arn: '',
endpoint_url: '',
addressing_style: 'virtual',
},
},
},
[DataSourceKey.R2]: {
name: '',
source: DataSourceKey.R2,
config: {
bucket_name: '',
credentials: {
account_id: '',
r2_access_key_id: '',
r2_secret_access_key: '',
},
},
},
[DataSourceKey.NOTION]: {
name: '',
source: DataSourceKey.NOTION,
config: {
root_page_id: '',
credentials: {
notion_integration_token: '',
},
},
},
[DataSourceKey.DISCORD]: {
name: '',
source: DataSourceKey.DISCORD,
config: {
server_ids: [],
channels: [],
credentials: {
discord_bot_token: '',
},
},
},
[DataSourceKey.CONFLUENCE]: {
name: '',
source: DataSourceKey.CONFLUENCE,
config: {
wiki_base: '',
is_cloud: true,
space: '',
page_id: '',
credentials: {
confluence_username: '',
confluence_access_token: '',
},
index_mode: 'everything',
},
},
[DataSourceKey.GOOGLE_DRIVE]: {
name: '',
source: DataSourceKey.GOOGLE_DRIVE,
config: {
include_shared_drives: false,
include_my_drives: true,
include_files_shared_with_me: false,
allow_images: false,
shared_drive_urls: '',
shared_folder_urls: '',
my_drive_emails: '',
specific_user_emails: '',
credentials: {
google_primary_admin: '',
google_tokens: '',
authentication_method: 'uploaded',
},
},
},
[DataSourceKey.GMAIL]: {
name: '',
source: DataSourceKey.GMAIL,
config: {
credentials: {
google_primary_admin: '',
google_tokens: '',
authentication_method: 'uploaded',
},
},
},
[DataSourceKey.GOOGLE_CLOUD_STORAGE]: {
name: '',
source: DataSourceKey.GOOGLE_CLOUD_STORAGE,
config: {
bucket_name: '',
credentials: {
access_key_id: '',
secret_access_key: '',
},
},
},
[DataSourceKey.OCI_STORAGE]: {
name: '',
source: DataSourceKey.OCI_STORAGE,
config: {
bucket_name: '',
credentials: {
namespace: '',
region: '',
access_key_id: '',
secret_access_key: '',
},
},
},
[DataSourceKey.MOODLE]: {
name: '',
source: DataSourceKey.MOODLE,
config: {
moodle_url: '',
credentials: {
moodle_token: '',
},
},
},
[DataSourceKey.TEAMS]: {
name: '',
source: DataSourceKey.TEAMS,
config: {
credentials: {
tenant_id: '',
client_id: '',
client_secret: '',
},
},
},
[DataSourceKey.SLACK]: {
name: '',
source: DataSourceKey.SLACK,
config: {
channels: [],
credentials: {
slack_bot_token: '',
},
},
},
[DataSourceKey.SHAREPOINT]: {
name: '',
source: DataSourceKey.SHAREPOINT,
config: {
credentials: {
site_url: '',
tenant_id: '',
client_id: '',
client_secret: '',
},
},
},
[DataSourceKey.JIRA]: {
name: '',
source: DataSourceKey.JIRA,
config: {
is_cloud: true,
base_url: '',
project_key: '',
jql_query: '',
batch_size: 2,
include_comments: true,
include_attachments: false,
attachment_size_limit: 10 * 1024 * 1024,
labels_to_skip: [],
comment_email_blacklist: [],
scoped_token: false,
credentials: {
jira_user_email: '',
jira_username: '',
jira_api_token: '',
jira_password: '',
},
},
},
[DataSourceKey.WEBDAV]: {
name: '',
source: DataSourceKey.WEBDAV,
config: {
base_url: '',
remote_path: '/',
credentials: {
username: '',
password: '',
},
},
},
[DataSourceKey.DROPBOX]: {
name: '',
source: DataSourceKey.DROPBOX,
config: {
batch_size: 2,
credentials: {
dropbox_access_token: '',
},
},
},
[DataSourceKey.BOX]: {
name: '',
source: DataSourceKey.BOX,
config: {
name: '',
folder_id: '0',
credentials: {
box_tokens: '',
},
},
},
[DataSourceKey.AIRTABLE]: {
name: '',
source: DataSourceKey.AIRTABLE,
config: {
name: '',
base_id: '',
table_name_or_id: '',
credentials: {
airtable_access_token: '',
},
},
},
[DataSourceKey.DINGTALK_AI_TABLE]: {
name: '',
source: DataSourceKey.DINGTALK_AI_TABLE,
config: {
table_id: '',
operator_id: '',
credentials: {
access_token: '',
},
},
},
[DataSourceKey.GITLAB]: {
name: '',
source: DataSourceKey.GITLAB,
config: {
project_owner: '',
project_name: '',
gitlab_url: 'https://gitlab.com',
include_mrs: true,
include_issues: true,
include_code_files: true,
credentials: {
gitlab_access_token: '',
},
},
},
[DataSourceKey.ASANA]: {
name: '',
source: DataSourceKey.ASANA,
config: {
name: '',
asana_workspace_id: '',
asana_project_ids: '',
asana_team_id: '',
credentials: {
asana_api_token_secret: '',
},
},
},
[DataSourceKey.GITHUB]: {
name: '',
source: DataSourceKey.GITHUB,
config: {
repository_owner: '',
repository_name: '',
include_pull_requests: true,
include_issues: true,
credentials: {
github_access_token: '',
},
},
},
[DataSourceKey.IMAP]: {
name: '',
source: DataSourceKey.IMAP,
config: {
name: '',
imap_host: '',
imap_port: 993,
imap_mailbox: [],
poll_range: 30,
credentials: {
imap_username: '',
imap_password: '',
},
},
},
[DataSourceKey.BITBUCKET]: {
name: '',
source: DataSourceKey.BITBUCKET,
config: {
workspace: '',
index_mode: 'workspace',
repository_slugs: '',
projects: '',
},
credentials: {
bitbucket_api_token: '',
},
},
[DataSourceKey.ZENDESK]: {
name: '',
source: DataSourceKey.ZENDESK,
config: {
name: '',
zendesk_content_type: 'articles',
credentials: {
zendesk_subdomain: '',
zendesk_email: '',
zendesk_token: '',
},
},
},
[DataSourceKey.SEAFILE]: {
name: '',
source: DataSourceKey.SEAFILE,
config: {
seafile_url: '',
sync_scope: 'account',
repo_id: '',
sync_path: '',
include_shared: true,
batch_size: 100,
credentials: {
seafile_token: '',
repo_token: '',
},
},
},
[DataSourceKey.MYSQL]: {
name: '',
source: DataSourceKey.MYSQL,
config: {
host: 'localhost',
port: 3306,
database: '',
query: '',
content_columns: '',
metadata_columns: '',
id_column: '',
timestamp_column: '',
credentials: {
username: '',
password: '',
},
},
},
[DataSourceKey.POSTGRESQL]: {
name: '',
source: DataSourceKey.POSTGRESQL,
config: {
host: 'localhost',
port: 5432,
database: '',
query: '',
content_columns: '',
metadata_columns: '',
id_column: '',
timestamp_column: '',
credentials: {
username: '',
password: '',
},
},
},
[DataSourceKey.ONEDRIVE]: {
name: '',
source: DataSourceKey.ONEDRIVE,
config: {
folder_path: '',
batch_size: 2,
credentials: {
tenant_id: '',
client_id: '',
client_secret: '',
},
},
},
[DataSourceKey.OUTLOOK]: {
name: '',
source: DataSourceKey.OUTLOOK,
config: {
folder: 'inbox',
user_ids: '',
batch_size: 2,
credentials: {
tenant_id: '',
client_id: '',
client_secret: '',
},
},
},
[DataSourceKey.REST_API]: {
name: '',
source: DataSourceKey.REST_API,
config: {
url: '',
method: 'GET',
query_params: '',
headers: '',
auth_type: 'none',
auth_config: {},
items_path: '',
id_field: '',
content_fields: '',
metadata_fields: '',
pagination_type: 'none',
pagination_config: {},
poll_timestamp_field: '',
request_body: '',
max_pages: 1000,
request_delay: 0.5,
show_advanced: false,
credentials: {
api_key: '',
token: '',
username: '',
password: '',
},
},
},
};
export const getDataSourceFieldsWithExtras = (
source?: DataSourceKey,
): FormFieldConfig[] => {
if (!source) {
return [];
}
const sourceFields =
DataSourceFormFields[source as keyof typeof DataSourceFormFields] || [];
const extraFields = getCommonExtraFields(source);
if (source !== DataSourceKey.JIRA) {
return [...sourceFields, ...extraFields];
}
const modeFieldIndex = sourceFields.findIndex(
(field) => field.name === 'config.is_cloud',
);
if (modeFieldIndex < 0) {
return [...sourceFields, ...extraFields];
}
const sharedFields = sourceFields.slice(0, modeFieldIndex);
const modeFields = sourceFields.slice(modeFieldIndex);
const sharedCheckboxFieldIndex = sharedFields.findIndex(
(field) => field.type === FormFieldType.Checkbox,
);
if (sharedCheckboxFieldIndex < 0) {
return [...sharedFields, ...extraFields, ...modeFields];
}
return [
...sharedFields.slice(0, sharedCheckboxFieldIndex),
...sharedFields.slice(sharedCheckboxFieldIndex),
...extraFields,
...modeFields,
];
};