Files
ragflow/web/src/pages/user-setting/data-source/constant/index.tsx
web-dev0521 98bc9ca6ac feat: implement Microsoft Teams data source connector (#15193)
### What problem does this PR solve?

Closes #15191.

RAGFlow shipped a Microsoft Teams connector stub
(`common/data_source/teams_connector.py`) whose document-loading methods
all returned `[]`, `Teams._generate()` was a `pass`, and Teams was
commented out of the data-source settings UI. As a result there was no
way to index Teams channel conversations into a knowledge base.

This PR implements the connector end to end on top of Microsoft Graph
(Office365-REST-Python-Client). It shares the MSAL client-credentials
auth shape with the SharePoint connector.

**Backend**

- `common/data_source/teams_connector.py`
- `load_credentials()` now builds the Graph client using an MSAL
client-credentials **token callback** — the form `GraphClient` actually
expects. (The previous stub passed a raw access-token string to
`GraphClient(...)`, which is not how that client is driven.) Token
acquisition is lazy, so credential loading performs no network call.
  - `validate_connector_settings()` lists teams via Graph.
- `load_from_checkpoint()` is now a generator that pages teams →
channels → messages, flattens each top-level post together with its
replies into one blob-based `Document` (`extension` `.txt`/`.html`,
`blob`, `size_bytes`, `doc_updated_at`). Incremental syncs are bounded
by message `lastModifiedDateTime` (falling back to `createdDateTime`).
Per-message errors surface as `ConnectorFailure` instead of aborting the
run.
- `retrieve_all_slim_docs_perm_sync()` yields id-only `SlimDocument`
batches and the checkpoint helpers return proper `TeamsCheckpoint`s.
- ACL → `ExternalAccess` mapping is intentionally left best-effort
(`load_from_checkpoint_with_perm_sync` delegates to the standard load)
because the sync pipeline does not currently persist `ExternalAccess`.
- `rag/svr/sync_data_source.py`
- Implemented `Teams._generate()` using the existing
`CheckpointOutputWrapper` pattern (same shape as Confluence/Jira/Google
Drive), supporting full reindex and incremental polling from
`poll_range_start`.
- `TeamsConnector` is already exported from
`common/data_source/__init__.py`.

**Frontend (`web/`)**

- Enabled the `TEAMS` data-source enum and added its form fields
(`tenant_id`, `client_id`, `client_secret`), default values, display
metadata, and a Teams icon.
- Added `teamsDescription` / `teamsTenantIdTip` to `en.ts` and `zh.ts`.

**Tests**

- `test/unit_test/data_source/test_teams_connector_unit.py`: mock-based
unit tests covering credential loading (incomplete creds raise, happy
path sets the Graph client, fetch-without-creds raises), post/reply
flattening (incl. the HTML vs text extension), incremental
`lastModifiedDateTime` filtering, and slim-doc listing. All 6 pass;
`ruff check` is clean.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2026-05-28 17:10:38 +08:00

1917 lines
49 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',
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.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} />,
},
};
};
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.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.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,
];
};