From 2ad99bf5cd08b6540edf3e326b914b8d025db0cf Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:52:21 +0800 Subject: [PATCH] fix(knowledge-fs): show installed datasource providers --- .../new-rag/create/__tests__/page.spec.tsx | 59 +++- web/features/new-rag/create/source-setup.tsx | 263 ++++++++++-------- .../connected-source-workflow.spec.tsx | 146 +++++++++- .../sources/create/__tests__/page.spec.tsx | 92 ++++-- .../create/connected-source-workflow.tsx | 159 ++++++----- web/features/new-rag/sources/create/page.tsx | 62 ++--- .../setup/__tests__/provider-options.spec.ts | 68 ++++- web/features/new-rag/sources/setup/fields.tsx | 94 +++---- .../new-rag/sources/setup/provider-options.ts | 67 +++-- .../new-rag/sources/source-edit-dialog.tsx | 13 +- 10 files changed, 668 insertions(+), 355 deletions(-) diff --git a/web/features/new-rag/create/__tests__/page.spec.tsx b/web/features/new-rag/create/__tests__/page.spec.tsx index 040d27afab5..2df1b919555 100644 --- a/web/features/new-rag/create/__tests__/page.spec.tsx +++ b/web/features/new-rag/create/__tests__/page.spec.tsx @@ -1184,7 +1184,7 @@ describe('CreateKnowledgePage', () => { ) }) - it('enables every atomic source type and distinguishes installed providers', async () => { + it('enables every atomic source type and only lists installed providers', async () => { const user = userEvent.setup() renderPage() @@ -1208,12 +1208,13 @@ describe('CreateKnowledgePage', () => { expect(onlineDocuments).toBeEnabled() expect(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDrive' })).toBeEnabled() expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked() - expect(screen.getByRole('radio', { name: 'Jina Reader' })).toBeEnabled() - expect(screen.getByRole('radio', { name: 'WaterCrawl' })).toBeEnabled() + expect(screen.queryByRole('radio', { name: 'Jina Reader' })).not.toBeInTheDocument() + expect(screen.queryByRole('radio', { name: 'WaterCrawl' })).not.toBeInTheDocument() await user.click(onlineDocuments) expect(onlineDocuments).toBeChecked() - expect(screen.getByRole('radio', { name: 'Notion' })).toBeChecked() - expect(screen.getByText('workflow.nodes.common.pluginNotInstalled')).toBeInTheDocument() + expect(screen.queryByRole('radio', { name: 'Notion' })).not.toBeInTheDocument() + expect(screen.getByRole('status')).toHaveTextContent('plugin.list.notFound') + expect(screen.queryByText('workflow.nodes.common.pluginNotInstalled')).not.toBeInTheDocument() await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.websiteCrawl' })) expect(screen.getByRole('button', { name: 'dataset.newKnowledge.moreProviders' })).toBeEnabled() expect(screen.queryByRole('alert')).not.toBeInTheDocument() @@ -1253,6 +1254,54 @@ describe('CreateKnowledgePage', () => { expect(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' })).toBeDisabled() }) + it('prompts for provider installation when source setup has no installed integration', async () => { + const user = userEvent.setup() + datasourceQueryMock.plugins.data = [] + datasourceQueryMock.auth.data = { result: [] } + + renderPage() + + await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.connectSource' })) + + expect(screen.getByRole('status')).toHaveTextContent('plugin.list.notFound') + expect(screen.queryByRole('radio', { name: 'Firecrawl' })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: 'dataset.newKnowledge.moreProviders' })).toBeEnabled() + }) + + it('clears a completed crawl when the installed provider changes during setup', async () => { + const user = userEvent.setup() + datasourceQueryMock.plugins.data = [firecrawlDatasourcePlugin, jinaDatasourcePlugin] + datasourceQueryMock.auth.data = { result: [firecrawlDatasourceAuth, jinaDatasourceAuth] } + const view = renderPage() + + await fillRequiredFields(user) + await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.connectSource' })) + await user.type( + screen.getByPlaceholderText('dataset.newKnowledge.rootUrlPlaceholder'), + 'https://docs.dify.ai', + ) + await user.type( + screen.getByPlaceholderText('dataset.newKnowledge.sourceNamePlaceholder'), + 'Dify docs', + ) + await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' })) + await user.click(await screen.findByRole('checkbox', { name: 'Getting started' })) + await waitFor(() => + expect( + screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }), + ).toBeEnabled(), + ) + + datasourceQueryMock.plugins.data = [jinaDatasourcePlugin] + datasourceQueryMock.auth.data = { result: [jinaDatasourceAuth] } + view.rerender() + + expect(screen.getByRole('radio', { name: 'Jina Reader' })).toBeChecked() + expect(screen.queryByText('Getting started')).not.toBeInTheDocument() + expect(screen.getByText('dataset.newKnowledge.pagesAppearTitle')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' })).toBeDisabled() + }) + it('disables upload before creating a space when direct upload is unavailable', () => { navigationMock.startMode = 'upload' systemFeaturesStateMock.uploadEnabled = false diff --git a/web/features/new-rag/create/source-setup.tsx b/web/features/new-rag/create/source-setup.tsx index ed844b76077..10a3ab4bccf 100644 --- a/web/features/new-rag/create/source-setup.tsx +++ b/web/features/new-rag/create/source-setup.tsx @@ -21,8 +21,6 @@ import { CrawlPreviewPageSelection } from '../sources/setup/crawl-selection' import { WebsiteDatasourceParameterForm } from '../sources/setup/datasource-parameter-form' import { datasourceIncludeSubpages, - datasourceParameterDefaults, - datasourceParameterSchemas, invalidDatasourceParameters, missingRequiredDatasourceParameters, websiteDatasourceParameterSchemas, @@ -31,14 +29,15 @@ import { import { SourceNameField, SourceProviderCredentialRequiredCard, + SourceProviderEmptyState, SourceProviderIcon, - SourceProviderNotInstalledCard, SourceProviderRadioGroup, SourceSyncPolicyField, SourceTypeSelector, } from '../sources/setup/fields' import { discoverSourceProviderOptions, + sourceDraftForProviderOption, sourceProviderOptionForDraft, } from '../sources/setup/provider-options' @@ -52,6 +51,13 @@ const CRAWL_POLL_INTERVAL_MS = 1500 type LocalCrawlState = 'error' | 'idle' | 'running' | 'stopped' | 'success' type InitialSource = NonNullable +type CreateSourceSetupProps = { + disabled: boolean + draft: NewKnowledgeSourceDraft + onDraftChange: (draft: NewKnowledgeSourceDraft) => void + onInitialSourceChange: (source?: InitialSource) => void + onSourceTypeChange: (sourceType: NewKnowledgeSourceDraft['sourceType']) => void +} function datasourceAuthForProvider( authProviders: DataSourceAuth[], @@ -90,19 +96,106 @@ function websiteSourceUri(parameters: Record, return `datasource://${encodeURIComponent(fallback)}` } -export function CreateSourceSetup({ +export function CreateSourceSetup(props: CreateSourceSetupProps) { + const { disabled, draft, onDraftChange, onSourceTypeChange } = props + const { t } = useTranslation('dataset') + const datasourcePluginsQuery = useDataSourceList(true) + const datasourceAuthQuery = useGetDataSourceListAuth() + const providerOptions = useMemo( + () => discoverSourceProviderOptions(draft.sourceType, datasourcePluginsQuery.data ?? []), + [datasourcePluginsQuery.data, draft.sourceType], + ) + const providerOption = sourceProviderOptionForDraft(providerOptions, draft) + const providerDraft = useMemo( + () => (providerOption ? sourceDraftForProviderOption(draft, providerOption) : draft), + [draft, providerOption], + ) + const datasourceAuth = providerOption + ? datasourceAuthForProvider( + datasourceAuthQuery.data?.result ?? [], + providerOption.plugin.plugin_id, + providerOption.plugin.provider, + ) + : undefined + const credential = preferredCredential(datasourceAuth) + const sessionKey = [ + draft.sourceType, + providerOption?.key ?? 'no-provider', + providerOption?.plugin.plugin_unique_identifier ?? 'no-plugin-version', + credential?.id ?? 'no-credential', + ].join(':') + + const selectProvider = (providerKey: string) => { + const nextProvider = providerOptions.find((option) => option.key === providerKey) + if (!nextProvider) return + onDraftChange(sourceDraftForProviderOption(providerDraft, nextProvider)) + } + + return ( +
+ + +
+ + {t(($) => $['newKnowledge.providerLabel'])} + +
+ + {t(($) => $['newKnowledge.providerLabel'])} + + +
+ {providerOptions.length > 0 ? ( + ({ + icon: ( + + ), + label: option.label, + value: option.key, + }))} + surface="default" + onChange={selectProvider} + /> + ) : !datasourcePluginsQuery.isPending && !datasourcePluginsQuery.error ? ( + + ) : null} +
+ + +
+ ) +} + +function CreateSourceSetupSession({ disabled, draft, onDraftChange, onInitialSourceChange, - onSourceTypeChange, -}: { - disabled: boolean - draft: NewKnowledgeSourceDraft - onDraftChange: (draft: NewKnowledgeSourceDraft) => void - onInitialSourceChange: (source?: InitialSource) => void - onSourceTypeChange: (sourceType: NewKnowledgeSourceDraft['sourceType']) => void -}) { +}: CreateSourceSetupProps) { const { t } = useTranslation('dataset') const datasourcePluginsQuery = useDataSourceList(true) const datasourceAuthQuery = useGetDataSourceListAuth() @@ -112,13 +205,16 @@ export function CreateSourceSetup({ const [selectedPageIds, setSelectedPageIds] = useState>(() => new Set()) const crawlAttemptRef = useRef(0) const previewJobIdRef = useRef(undefined) - const sourceType = draft.sourceType const providerOptions = useMemo( - () => discoverSourceProviderOptions(sourceType, datasourcePluginsQuery.data ?? []), - [datasourcePluginsQuery.data, sourceType], + () => discoverSourceProviderOptions(draft.sourceType, datasourcePluginsQuery.data ?? []), + [datasourcePluginsQuery.data, draft.sourceType], ) const providerOption = sourceProviderOptionForDraft(providerOptions, draft) - const installedProviderOption = providerOption?.installed ? providerOption : undefined + const providerDraft = useMemo( + () => (providerOption ? sourceDraftForProviderOption(draft, providerOption) : draft), + [draft, providerOption], + ) + const installedProviderOption = providerOption const datasourceAuth = installedProviderOption ? datasourceAuthForProvider( datasourceAuthQuery.data?.result ?? [], @@ -135,26 +231,26 @@ export function CreateSourceSetup({ [draft.sourceType, installedProviderOption], ) const parameters = useMemo(() => { - const current = withDatasourceParameterDefaults(parameterSchemas, draft.parameters) + const current = withDatasourceParameterDefaults(parameterSchemas, providerDraft.parameters) if ( - draft.sourceType === 'websiteCrawl' && - draft.rootUrl && + providerDraft.sourceType === 'websiteCrawl' && + providerDraft.rootUrl && parameterSchemas.some((parameter) => parameter.name === 'url') && current.url === undefined ) - current.url = draft.rootUrl + current.url = providerDraft.rootUrl return current - }, [draft, parameterSchemas]) + }, [parameterSchemas, providerDraft]) const parametersValid = !missingRequiredDatasourceParameters(parameterSchemas, parameters).length && !invalidDatasourceParameters(parameterSchemas, parameters).length const selectionPages = previewPages const previewReady = Boolean( - draft.sourceType === 'websiteCrawl' && + providerDraft.sourceType === 'websiteCrawl' && credential && installedProviderOption && parametersValid && - draft.sourceName.trim(), + providerDraft.sourceName.trim(), ) const sourceUri = installedProviderOption ? websiteSourceUri(parameters, installedProviderOption.key) @@ -227,25 +323,6 @@ export function CreateSourceSetup({ const updateDraftWithoutReset = (nextDraft: NewKnowledgeSourceDraft) => { onDraftChange(nextDraft) } - const selectProvider = (providerKey: string) => { - const nextProvider = providerOptions.find((option) => option.key === providerKey) - if (!nextProvider) return - updateDraft({ - ...draft, - parameters: nextProvider.installed - ? datasourceParameterDefaults( - draft.sourceType === 'websiteCrawl' - ? websiteDatasourceParameterSchemas(nextProvider.datasource) - : datasourceParameterSchemas(nextProvider.datasource), - ) - : {}, - provider: nextProvider.label, - providerKey: nextProvider.key, - sourceName: '', - ...(draft.sourceType === 'websiteCrawl' ? { rootUrl: '' } : {}), - }) - } - useEffect( () => () => { crawlAttemptRef.current += 1 @@ -256,13 +333,14 @@ export function CreateSourceSetup({ params: { job_id: jobId }, }) .catch(() => {}) + onInitialSourceChange(undefined) }, - [], + [onInitialSourceChange], ) const startPreview = async () => { if ( - draft.sourceType !== 'websiteCrawl' || + providerDraft.sourceType !== 'websiteCrawl' || !previewReady || !credential || !installedProviderOption @@ -342,7 +420,7 @@ export function CreateSourceSetup({ } useEffect(() => { - if (draft.sourceType !== 'websiteCrawl') { + if (providerDraft.sourceType !== 'websiteCrawl') { if (!installedProviderOption || !credential) onInitialSourceChange(undefined) return } @@ -368,7 +446,7 @@ export function CreateSourceSetup({ credentialId: credential.id, datasource: installedProviderOption.datasource.identity.name, kind: 'website_crawl', - name: draft.sourceName.trim(), + name: providerDraft.sourceName.trim(), pluginId: installedProviderOption.plugin.plugin_id, provider: installedProviderOption.plugin.provider, providerDisplayName: installedProviderOption.label, @@ -378,70 +456,25 @@ export function CreateSourceSetup({ source_url: page.sourceUrl, ...(page.title ? { title: page.title } : {}), })), - ...(draft.syncPolicy === 'custom' && draft.customIntervalSeconds - ? { custom_interval_seconds: draft.customIntervalSeconds } + ...(providerDraft.syncPolicy === 'custom' && providerDraft.customIntervalSeconds + ? { custom_interval_seconds: providerDraft.customIntervalSeconds } : {}), - sync_policy: draft.syncPolicy, + sync_policy: providerDraft.syncPolicy, }) }, [ crawlState, credential, - draft, installedProviderOption, onInitialSourceChange, parameters, + providerDraft, selectedPageIds, selectionPages, sourceUri, ]) return ( -
- { - onSourceTypeChange(value) - }} - /> - -
- - {t(($) => $['newKnowledge.providerLabel'])} - -
- - {t(($) => $['newKnowledge.providerLabel'])} - - -
- ({ - icon: , - label: option.label, - value: option.key, - }))} - surface="default" - onChange={selectProvider} - /> -
- + <> {datasourcePluginsQuery.isPending || datasourceAuthQuery.isPending ? (
@@ -460,22 +493,18 @@ export function CreateSourceSetup({ {t(($) => $['newKnowledge.retryProviderLoad'])}
- ) : providerOption && !providerOption.installed ? ( - } - provider={providerOption.label} - onInstall={() => - globalThis.open( - providerIntegrationPath(providerOption.packageId), - '_blank', - 'noopener,noreferrer', - ) - } - /> ) : installedProviderOption && !credential ? ( } + icon={ + + } provider={installedProviderOption.label} onConnect={() => globalThis.open( @@ -485,13 +514,13 @@ export function CreateSourceSetup({ ) } /> - ) : draft.sourceType === 'websiteCrawl' && installedProviderOption && credential ? ( + ) : providerDraft.sourceType === 'websiteCrawl' && installedProviderOption && credential ? (
@@ -501,7 +530,7 @@ export function CreateSourceSetup({ schemas={parameterSchemas} onChange={(nextParameters) => updateDraft({ - ...draft, + ...providerDraft, parameters: nextParameters, rootUrl: typeof nextParameters.url === 'string' ? nextParameters.url : '', }) @@ -617,16 +646,16 @@ export function CreateSourceSetup({
- ) : draft.sourceType !== 'websiteCrawl' && installedProviderOption && credential ? ( + ) : providerDraft.sourceType !== 'websiteCrawl' && installedProviderOption && credential ? ( ) : null} -
+ ) } diff --git a/web/features/new-rag/sources/create/__tests__/connected-source-workflow.spec.tsx b/web/features/new-rag/sources/create/__tests__/connected-source-workflow.spec.tsx index 4ffc34b21ad..ceb0f68b722 100644 --- a/web/features/new-rag/sources/create/__tests__/connected-source-workflow.spec.tsx +++ b/web/features/new-rag/sources/create/__tests__/connected-source-workflow.spec.tsx @@ -631,25 +631,151 @@ describe('ConnectedSourceWorkflow', () => { expect(clientMock.createConnection).not.toHaveBeenCalled() }) - it('distinguishes an uninstalled provider from an installed provider without credentials', async () => { - const user = userEvent.setup() + it('falls back to an installed provider when the requested provider is not installed', async () => { renderSetup({ ...defaultDraft, provider: 'Confluence', }) - expect(await screen.findByText('workflow.nodes.common.pluginNotInstalled')).toBeInTheDocument() + expect(screen.queryByRole('radio', { name: 'Confluence' })).not.toBeInTheDocument() + expect(await screen.findByRole('radio', { name: 'Notion' })).toBeChecked() expect( - screen.queryByText('dataset.newKnowledge.providerNotConfigured:{"provider":"Notion"}'), - ).not.toBeInTheDocument() + await screen.findByText('dataset.newKnowledge.providerNotConfigured:{"provider":"Notion"}'), + ).toBeInTheDocument() + expect(screen.queryByText('workflow.nodes.common.pluginNotInstalled')).not.toBeInTheDocument() expect(clientMock.createConnection).not.toHaveBeenCalled() + }) - await user.click(screen.getByRole('button', { name: 'plugin.installPlugin' })) - expect(openMock).toHaveBeenCalledWith( - '/integrations/data-source?package-ids=%5B%22langgenius%2Fconfluence_datasource%22%5D', - '_blank', - 'noopener,noreferrer', + it('uses the fallback provider transport and clears stale provider parameters', async () => { + clientMock.listDatasourceAuth.mockResolvedValue({ + result: [notionDatasourceAuth([notionCredential])], + }) + clientMock.createConnection.mockResolvedValue(connectionResponse()) + + renderSetup({ + ...defaultDraft, + parameters: { folderId: 'stale-google-folder' }, + provider: 'Google Docs', + sourceName: 'Old Google source', + }) + + await waitFor(() => + expect(clientMock.createConnection).toHaveBeenCalledWith({ + body: { + authKind: 'endpoint', + configuration: { + credentialId: notionCredential.id, + datasource: 'notion', + pluginId: 'langgenius/notion_datasource', + provider: 'notion', + providerKind: 'online-document', + }, + credentials: {}, + name: notionCredential.name, + providerId: notionProvider.id, + }, + params: { control_space_id: 'space-1' }, + }), ) + await waitFor(() => + expect(clientMock.createSource).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + metadata: expect.objectContaining({ + parameters: {}, + providerKind: 'online-document', + providerName: 'Notion', + }), + name: 'Notion', + }), + }), + ), + ) + }) + + it('prompts for provider installation when no matching integration is installed', async () => { + clientMock.listDatasourcePlugins.mockResolvedValue([s3DatasourcePlugin]) + + renderSetup() + + const prompt = await screen.findByText('plugin.list.notFound') + expect(prompt.closest('[role="status"]')).toBeInTheDocument() + expect(screen.queryByRole('radio', { name: 'Notion' })).not.toBeInTheDocument() + expect(screen.queryByText('dataset.newKnowledge.providerUnavailable')).not.toBeInTheDocument() + }) + + it('clears a failed connection attempt when a provider refresh selects another integration', async () => { + const outlineProvider: KnowledgeFsSourceProviderResponse = { + ...notionProvider, + display_name: 'Outline', + id: 'outline-provider', + } + const outlineDatasourcePlugin: DataSourceItem = { + ...notionDatasourcePlugin, + declaration: { + ...notionDatasourcePlugin.declaration, + datasources: [ + { + ...notionDatasourcePlugin.declaration.datasources[0]!, + identity: { + ...notionDatasourcePlugin.declaration.datasources[0]!.identity, + label: { en_US: 'Outline', zh_Hans: 'Outline' }, + name: 'outline', + provider: 'outline', + }, + }, + ], + identity: { + ...notionDatasourcePlugin.declaration.identity, + label: { en_US: 'Outline', zh_Hans: 'Outline' }, + name: 'outline', + }, + }, + plugin_id: 'langgenius/outline_datasource', + plugin_unique_identifier: 'langgenius/outline_datasource:1.0.0@local', + provider: 'outline', + } + const outlineDatasourceAuth: DataSourceAuth = { + ...notionDatasourceAuth(), + label: { en_US: 'Outline', zh_Hans: 'Outline' }, + name: 'outline', + plugin_id: outlineDatasourcePlugin.plugin_id, + plugin_unique_identifier: outlineDatasourcePlugin.plugin_unique_identifier, + provider: outlineDatasourcePlugin.provider, + } + clientMock.listDatasourceAuth.mockResolvedValue({ + result: [notionDatasourceAuth([notionCredential])], + }) + clientMock.createConnection.mockRejectedValue(new Error('provider unavailable')) + const { queryClient } = renderSetup() + + expect( + await screen.findByText('dataset.newKnowledge.connectionFailed:{"provider":"Notion"}'), + ).toHaveAttribute('role', 'alert') + + clientMock.listProviders.mockResolvedValue({ + data: [outlineProvider], + } satisfies KnowledgeFsSourceProviderListResponse) + await act(async () => { + queryClient.setQueryData(['pipeline', 'datasource'], [outlineDatasourcePlugin]) + queryClient.setQueryData(['data-source-auth', 'list'], { + result: [outlineDatasourceAuth], + }) + await queryClient.refetchQueries({ queryKey: ['source-providers'] }) + }) + + expect(await screen.findByRole('radio', { name: 'Outline' })).toBeChecked() + expect( + await screen.findByText('dataset.newKnowledge.providerNotConfigured:{"provider":"Outline"}'), + ).toBeInTheDocument() + expect( + screen.queryByText('dataset.newKnowledge.connectionFailed:{"provider":"Outline"}'), + ).not.toBeInTheDocument() + expect( + screen.getByRole('button', { + name: 'dataset.newKnowledge.connectProvider:{"provider":"Outline"}', + }), + ).toBeEnabled() }) it('starts the selected Notion import and completes setup without waiting for indexing', async () => { diff --git a/web/features/new-rag/sources/create/__tests__/page.spec.tsx b/web/features/new-rag/sources/create/__tests__/page.spec.tsx index d47713358d4..a4455137333 100644 --- a/web/features/new-rag/sources/create/__tests__/page.spec.tsx +++ b/web/features/new-rag/sources/create/__tests__/page.spec.tsx @@ -727,8 +727,7 @@ describe('AddSourcePage', () => { ) }) - it('keeps the exact website provider selected while loading website dependencies', async () => { - const user = userEvent.setup() + it('falls back to an installed website provider when the requested provider is not installed', () => { render( { />, ) - expect(screen.getByRole('radio', { name: 'Jina Reader' })).toBeChecked() - expect(providerHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: true }) - expect(connectionHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: true }) - expect(screen.getByText('workflow.nodes.common.pluginNotInstalled')).toBeInTheDocument() - await user.click( - screen.getByRole('button', { - name: 'plugin.installPlugin', - }), - ) - expect(openMock).toHaveBeenCalledWith( - '/integrations/data-source?package-ids=%5B%22langgenius%2Fjina_datasource%22%5D', - '_blank', - 'noopener,noreferrer', - ) - await user.click(screen.getByRole('radio', { name: 'Firecrawl' })) + expect(screen.queryByRole('radio', { name: 'Jina Reader' })).not.toBeInTheDocument() expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked() expect(providerHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: true }) expect(connectionHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: true }) + expect(screen.queryByText('workflow.nodes.common.pluginNotInstalled')).not.toBeInTheDocument() }) - it('keeps crawl fields hidden until the selected website provider is configured', async () => { + it('clears provider-specific draft fields when falling back to another website provider', async () => { const user = userEvent.setup() + queryState.connections.data = { pages: [{ items: [connection('active')] }] } + + render( + , + ) + + expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked() + expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ })).toHaveValue('') + expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.sourceName/ })).toHaveValue( + '', + ) + await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlOptions' })) + expect( + screen.getByRole('checkbox', { name: 'dataset.newKnowledge.includeSubpages' }), + ).toBeChecked() + expect(screen.getByRole('spinbutton', { name: 'dataset.newKnowledge.maxPages' })).toHaveValue( + 100, + ) + }) + + it('keeps crawl fields hidden until the selected website provider is configured', () => { render( { ).not.toBeInTheDocument() expect(screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' })).toBeEnabled() - await user.click(screen.getByRole('radio', { name: 'Jina Reader' })) - expect(screen.getByText('workflow.nodes.common.pluginNotInstalled')).toBeInTheDocument() + expect(screen.queryByRole('radio', { name: 'Jina Reader' })).not.toBeInTheDocument() + expect(screen.queryByText('workflow.nodes.common.pluginNotInstalled')).not.toBeInTheDocument() expect( screen.queryByRole('textbox', { name: 'dataset.newKnowledge.rootUrl' }), ).not.toBeInTheDocument() @@ -1245,6 +1268,19 @@ describe('AddSourcePage', () => { expect(moreProvidersLink).toHaveAttribute('rel', 'noopener noreferrer') }) + it('prompts for provider installation when no datasource integration is installed', () => { + queryState.datasourcePlugins.data = [] + + render() + + expect(screen.getByRole('status')).toHaveTextContent('plugin.list.notFound') + expect(screen.queryByRole('radio', { name: 'Firecrawl' })).not.toBeInTheDocument() + expect( + screen.getByRole('link', { name: 'dataset.newKnowledge.moreProviders' }), + ).toHaveAttribute('href', '/integrations/data-source') + expect(screen.queryByText('dataset.newKnowledge.providerUnavailable')).not.toBeInTheDocument() + }) + it('keeps the handed-off website draft when the provider connection becomes active', async () => { const initialSourceDraft = { includeSubpages: true, @@ -1578,7 +1614,7 @@ describe('AddSourcePage', () => { }) expect(onlineDocuments).toBeEnabled() expect(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDrive' })).toBeEnabled() - expect(screen.getByRole('radio', { name: 'Jina Reader' })).toBeEnabled() + expect(screen.queryByRole('radio', { name: 'Jina Reader' })).not.toBeInTheDocument() expect( screen.getByRole('group', { name: 'dataset.newKnowledge.providerLabel' }), ).toBeInTheDocument() @@ -1613,7 +1649,6 @@ describe('AddSourcePage', () => { }) it.each([ - ['websiteCrawl', 'Jina Reader'], ['onlineDocuments', 'Confluence'], ['onlineDrive', 'OneDrive'], ] as const)('restores the %s provider from a shortcut URL', (initialSourceType, provider) => { @@ -1628,6 +1663,19 @@ describe('AddSourcePage', () => { expect(screen.getByRole('radio', { name: provider })).toBeChecked() }) + it('does not expose an uninstalled website provider from a shortcut URL', () => { + render( + , + ) + + expect(screen.queryByRole('radio', { name: 'Jina Reader' })).not.toBeInTheDocument() + expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked() + }) + it('disables the final Add source action while its backend dependency is missing', async () => { const user = userEvent.setup() render() diff --git a/web/features/new-rag/sources/create/connected-source-workflow.tsx b/web/features/new-rag/sources/create/connected-source-workflow.tsx index 9c2fed5ea5f..77096d1047a 100644 --- a/web/features/new-rag/sources/create/connected-source-workflow.tsx +++ b/web/features/new-rag/sources/create/connected-source-workflow.tsx @@ -39,7 +39,6 @@ import { } from '../connections/model' import { DatasourceParameterForm } from '../setup/datasource-parameter-form' import { - datasourceParameterDefaults, datasourceParameterSchemas, invalidDatasourceParameters, missingRequiredDatasourceParameters, @@ -49,13 +48,13 @@ import { SourceNameField, SourceProviderCredentialRequiredCard, SourceProviderIcon, - SourceProviderNotInstalledCard, SourceProviderSelector, SourceSyncPolicyField, } from '../setup/fields' import { discoverSourceProviderOptions, normalizeSourceProviderName, + sourceDraftForProviderOption, sourceProviderOptionForDraft, } from '../setup/provider-options' import { NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH } from '../setup/source-draft' @@ -71,6 +70,13 @@ import { type ConnectedSourceDraft = | NewKnowledgeOnlineDocumentsSourceDraft | NewKnowledgeOnlineDriveSourceDraft +type ConnectedSourceWorkflowProps = { + draft: ConnectedSourceDraft + knowledgeSpaceId: string + onCompleted: () => void + onDraftChange: (draft: NewKnowledgeSourceDraft) => void + onExit: () => void +} type PageResource = { ancestorKeys: string[] depth: number @@ -136,7 +142,7 @@ function providerForDraft( } function datasourceProviderForOption(option?: SourceProviderOption) { - return option?.installed + return option ? { datasource: option.datasource, plugin: option.plugin, @@ -1493,19 +1499,62 @@ function AppliedResourceConfiguration({ ) } -export function ConnectedSourceWorkflow({ +export function ConnectedSourceWorkflow(props: ConnectedSourceWorkflowProps) { + const { draft, onDraftChange } = props + const datasourcePluginsQuery = useDataSourceList(true) + const datasourceAuthQuery = useGetDataSourceListAuth() + const providerOptions = useMemo( + () => discoverSourceProviderOptions(draft.sourceType, datasourcePluginsQuery.data ?? []), + [datasourcePluginsQuery.data, draft.sourceType], + ) + const providerOption = sourceProviderOptionForDraft(providerOptions, draft) + const providerDraft = useMemo( + () => (providerOption ? sourceDraftForProviderOption(draft, providerOption) : draft), + [draft, providerOption], + ) + const datasourceProvider = datasourceProviderForOption(providerOption) + const datasourceAuth = datasourceAuthForProvider( + datasourceAuthQuery.data?.result ?? [], + datasourceProvider, + ) + const credential = preferredCredential(datasourceAuth) + const sessionKey = [ + draft.sourceType, + providerOption?.key ?? 'no-provider', + providerOption?.plugin.plugin_unique_identifier ?? 'no-plugin-version', + credential?.id ?? 'no-credential', + ].join(':') + + const selectProvider = (providerKey: string) => { + const nextProvider = providerOptions.find((option) => option.key === providerKey) + if (!nextProvider) return + onDraftChange(sourceDraftForProviderOption(providerDraft, nextProvider)) + } + + return ( +
+ + +
+ ) +} + +function ConnectedSourceWorkflowSession({ draft, knowledgeSpaceId, onCompleted, onDraftChange, onExit, -}: { - draft: ConnectedSourceDraft - knowledgeSpaceId: string - onCompleted: () => void - onDraftChange: (draft: NewKnowledgeSourceDraft) => void - onExit: () => void -}) { +}: ConnectedSourceWorkflowProps) { const { t } = useTranslation('dataset') const queryClient = useQueryClient() const providersQuery = useQuery( @@ -1538,21 +1587,25 @@ export function ConnectedSourceWorkflow({ [datasourcePluginsQuery.data, draft.sourceType], ) const providerOption = sourceProviderOptionForDraft(providerOptions, draft) - const installedProviderOption = providerOption?.installed ? providerOption : undefined + const providerDraft = useMemo( + () => (providerOption ? sourceDraftForProviderOption(draft, providerOption) : draft), + [draft, providerOption], + ) + const installedProviderOption = providerOption const parameterSchemas = useMemo( () => installedProviderOption ? datasourceParameterSchemas(installedProviderOption.datasource) : [], [installedProviderOption], ) const parameters = useMemo( - () => withDatasourceParameterDefaults(parameterSchemas, draft.parameters), - [draft.parameters, parameterSchemas], + () => withDatasourceParameterDefaults(parameterSchemas, providerDraft.parameters), + [parameterSchemas, providerDraft.parameters], ) const parametersValid = !missingRequiredDatasourceParameters(parameterSchemas, parameters).length && !invalidDatasourceParameters(parameterSchemas, parameters).length - const provider = providerForDraft(providersQuery.data ?? [], draft, providerOption) - const driveTransport = usesDriveTransport(draft) + const provider = providerForDraft(providersQuery.data ?? [], providerDraft, providerOption) + const driveTransport = usesDriveTransport(providerDraft) const datasourceProvider = datasourceProviderForOption(installedProviderOption) const datasourceAuth = datasourceAuthForProvider( datasourceAuthQuery.data?.result ?? [], @@ -1659,7 +1712,7 @@ export function ConnectedSourceWorkflow({ providerKind: driveTransport ? 'online-drive' : 'online-document', }, credentials: {}, - name: credential.name || draft.provider, + name: credential.name || providerDraft.provider, providerId: provider.id, }, params: { control_space_id: knowledgeSpaceId }, @@ -1687,9 +1740,9 @@ export function ConnectedSourceWorkflow({ datasourceIdentity, datasourceProvider, driveTransport, - draft.provider, knowledgeSpaceId, provider, + providerDraft.provider, provisioningConnection, refetchConnections, rememberConnection, @@ -1716,40 +1769,8 @@ export function ConnectedSourceWorkflow({ providersQuery.isPending, provisionConnection, ]) - const selectProvider = (providerKey: string) => { - const nextProvider = providerOptions.find((option) => option.key === providerKey) - if (!nextProvider) return - setConnectionOverride(undefined) - setProvisionError(false) - if (draft.sourceType === 'onlineDocuments') { - onDraftChange({ - ...draft, - parameters: nextProvider.installed - ? datasourceParameterDefaults(datasourceParameterSchemas(nextProvider.datasource)) - : {}, - provider: nextProvider.label, - providerKey: nextProvider.key, - sourceName: '', - }) - return - } - onDraftChange({ - ...draft, - parameters: nextProvider.installed - ? datasourceParameterDefaults(datasourceParameterSchemas(nextProvider.datasource)) - : {}, - provider: nextProvider.label, - providerKey: nextProvider.key, - sourceName: '', - }) - } return ( -
- + <> {providersQuery.isPending || datasourcePluginsQuery.isPending || datasourceAuthQuery.isPending || @@ -1776,21 +1797,13 @@ export function ConnectedSourceWorkflow({ {t(($) => $['newKnowledge.retryProviderLoad'])}
- ) : providerOption && !providerOption.installed ? ( - } - provider={providerOption.label} - onInstall={() => - globalThis.open( - providerIntegrationPath(providerOption), - '_blank', - 'noopener,noreferrer', - ) - } - /> - ) : !installedProviderOption || !provider ? ( + ) : providerOptions.length === 0 ? null : !installedProviderOption ? ( +
+ {t(($) => $['newKnowledge.providerUnavailable'])} +
+ ) : !provider ? (
-

{draft.provider}

+

{installedProviderOption.label}

{t(($) => $['newKnowledge.providerUnavailable'])}

@@ -1806,7 +1819,7 @@ export function ConnectedSourceWorkflow({ ) : connection?.status === 'provisioning' ? (
@@ -1837,7 +1852,7 @@ export function ConnectedSourceWorkflow({

{t(($) => $['newKnowledge.providerCredentialRequiredDescription'], { - provider: draft.provider, + provider: providerDraft.provider, })}

@@ -1851,7 +1866,7 @@ export function ConnectedSourceWorkflow({ ) } > - {t(($) => $['newKnowledge.connectProvider'], { provider: draft.provider })} + {t(($) => $['newKnowledge.connectProvider'], { provider: providerDraft.provider })}
)} -
+ ) } diff --git a/web/features/new-rag/sources/create/page.tsx b/web/features/new-rag/sources/create/page.tsx index ac280a511ed..be2d888516a 100644 --- a/web/features/new-rag/sources/create/page.tsx +++ b/web/features/new-rag/sources/create/page.tsx @@ -36,19 +36,15 @@ import { sourceConnectionStatusRank, sourceProviderUsesManagedConfiguration, } from '../connections/model' -import { - datasourceParameterDefaults, - websiteDatasourceParameterSchemas, -} from '../setup/datasource-parameter-model' import { SourceProviderIcon, - SourceProviderNotInstalledCard, SourceProviderSelector, SourceSyncPolicyField, SourceTypeSelector, } from '../setup/fields' import { discoverSourceProviderOptions, + sourceDraftForProviderOption, sourceProviderOptionForDraft, } from '../setup/provider-options' import { @@ -571,10 +567,8 @@ function UnconfiguredProvider({ @@ -847,8 +841,15 @@ function AddSourcePageContent({ [datasourcePluginsQuery.data], ) const websiteProviderOption = sourceProviderOptionForDraft(websiteProviderOptions, sourceDraft) - const websiteProviderName = websiteProviderOption?.label ?? sourceDraft.provider - const datasourceProvider = websiteProviderOption?.installed ? websiteProviderOption : undefined + const providerDraft = useMemo( + () => + sourceDraft.sourceType === 'websiteCrawl' && websiteProviderOption + ? sourceDraftForProviderOption(sourceDraft, websiteProviderOption) + : sourceDraft, + [sourceDraft, websiteProviderOption], + ) + const websiteProviderName = websiteProviderOption?.label ?? providerDraft.provider + const datasourceProvider = websiteProviderOption const datasourceProviders = datasourceAuthQuery.data?.result ?? [] const datasourceCredential = findDatasourceCredential(datasourceProviders, datasourceProvider) const difyManagedProvider = provider ? isDifyManagedProvider(provider) : false @@ -1041,35 +1042,26 @@ function AddSourcePageContent({ disabled={websiteSetupLocked} value={sourceType} onChange={(value) => { - sourceDraftsRef.current[sourceDraft.sourceType] = sourceDraft + sourceDraftsRef.current[providerDraft.sourceType] = providerDraft updateSourceDraft( sourceDraftsRef.current[value] ?? createNewKnowledgeSourceDraft(value), ) }} /> - {sourceDraft.sourceType === 'websiteCrawl' ? ( + {providerDraft.sourceType === 'websiteCrawl' ? ( <> { const nextProvider = websiteProviderOptions.find( (option) => option.key === providerKey, ) if (!nextProvider) return - updateSourceDraft({ - ...sourceDraft, - parameters: nextProvider.installed - ? datasourceParameterDefaults( - websiteDatasourceParameterSchemas(nextProvider.datasource), - ) - : {}, - provider: nextProvider.label, - providerKey: nextProvider.key, - rootUrl: '', - }) + updateSourceDraft(sourceDraftForProviderOption(providerDraft, nextProvider)) }} /> {queryError ? ( @@ -1091,19 +1083,7 @@ function AddSourcePageContent({ {t(($) => $['newKnowledge.retryProviderLoad'])}
- ) : websiteProviderOption && !websiteProviderOption.installed ? ( - } - provider={websiteProviderOption.label} - onInstall={() => - globalThis.open( - websiteProviderIntegrationPath(websiteProviderOption), - '_blank', - 'noopener,noreferrer', - ) - } - /> - ) : !datasourceProvider || !provider ? ( + ) : websiteProviderOptions.length === 0 ? null : !datasourceProvider || !provider ? (
{t(($) => $['newKnowledge.providerUnavailable'])}
@@ -1118,7 +1098,7 @@ function AddSourcePageContent({ @@ -1174,7 +1154,7 @@ function AddSourcePageContent({ ) : ( { clearStoredSourceDraft() @@ -1189,7 +1169,7 @@ function AddSourcePageContent({ {sourceType === 'websiteCrawl' && !websiteReady && ( diff --git a/web/features/new-rag/sources/setup/__tests__/provider-options.spec.ts b/web/features/new-rag/sources/setup/__tests__/provider-options.spec.ts index a2802641f8e..a33d9e8722f 100644 --- a/web/features/new-rag/sources/setup/__tests__/provider-options.spec.ts +++ b/web/features/new-rag/sources/setup/__tests__/provider-options.spec.ts @@ -1,4 +1,22 @@ -import { sourceProviderPresentation } from '../provider-options' +import type { SourceProviderOption } from '../provider-options' +import { sourceDraftForProviderOption, sourceProviderPresentation } from '../provider-options' + +function providerOption({ key, label }: { key: string; label: string }) { + return { + datasource: { + parameters: [ + { + default: 'new-default', + label: { en_US: 'Workspace' }, + name: 'workspace', + type: 'string', + }, + ], + }, + key, + label, + } as SourceProviderOption +} describe('sourceProviderPresentation', () => { it.each([ @@ -19,3 +37,51 @@ describe('sourceProviderPresentation', () => { expect(sourceProviderPresentation('Notion Backup', 'onlineDocuments')).toBeUndefined() }) }) + +describe('sourceDraftForProviderOption', () => { + it('resets provider-specific fields when a different provider has the same label', () => { + const draft = { + parameters: { oldWorkspace: 'stale-value' }, + provider: 'Docs', + providerKey: 'onlineDocuments:old-provider', + sourceName: 'Existing source', + sourceType: 'onlineDocuments' as const, + syncPolicy: 'daily' as const, + } + + expect( + sourceDraftForProviderOption( + draft, + providerOption({ key: 'onlineDocuments:new-provider', label: 'Docs' }), + ), + ).toEqual({ + parameters: { workspace: 'new-default' }, + provider: 'Docs', + providerKey: 'onlineDocuments:new-provider', + sourceName: '', + sourceType: 'onlineDocuments', + syncPolicy: 'daily', + }) + }) + + it('preserves provider fields when the stable provider key matches after a label change', () => { + const draft = { + parameters: { workspace: 'saved-workspace' }, + provider: 'Old Docs Name', + providerKey: 'onlineDocuments:provider', + sourceName: 'Existing source', + sourceType: 'onlineDocuments' as const, + syncPolicy: 'daily' as const, + } + + expect( + sourceDraftForProviderOption( + draft, + providerOption({ key: 'onlineDocuments:provider', label: 'New Docs Name' }), + ), + ).toEqual({ + ...draft, + provider: 'New Docs Name', + }) + }) +}) diff --git a/web/features/new-rag/sources/setup/fields.tsx b/web/features/new-rag/sources/setup/fields.tsx index fa04e049595..6fefdd9af08 100644 --- a/web/features/new-rag/sources/setup/fields.tsx +++ b/web/features/new-rag/sources/setup/fields.tsx @@ -128,6 +128,26 @@ export function SourceProviderRadioGroup({ ) } +export function SourceProviderEmptyState({ className }: { className?: string }) { + const { t } = useTranslation('plugin') + + return ( +
+ + + + {t(($) => $['list.notFound'])} +
+ ) +} + type SourceProviderIconValue = | InstalledSourceProviderOption['datasource']['identity']['icon'] | InstalledSourceProviderOption['plugin']['declaration']['identity']['icon'] @@ -174,6 +194,7 @@ export function SourceProviderSelector({ layout = 'grid-three', options, providerKey, + showEmptyState = false, onChange, }: { appearance?: 'embedded' | 'page' @@ -181,6 +202,7 @@ export function SourceProviderSelector({ layout?: 'grid-four' | 'grid-three' options: SourceProviderOption[] providerKey: string + showEmptyState?: boolean onChange: (providerKey: string) => void }) { const { t } = useTranslation('dataset') @@ -213,28 +235,28 @@ export function SourceProviderSelector({ - ({ - icon: ( - - ), - label: option.label, - value: option.key, - }))} - size="medium" - surface="default" - onChange={onChange} - /> + {options.length > 0 ? ( + ({ + icon: ( + + ), + label: option.label, + value: option.key, + }))} + size="medium" + surface="default" + onChange={onChange} + /> + ) : showEmptyState ? ( + + ) : null} ) } @@ -384,31 +406,3 @@ export function SourceProviderCredentialRequiredCard({ /> ) } - -export function SourceProviderNotInstalledCard({ - icon, - provider, - onInstall, -}: { - icon: ReactNode - provider: string - onInstall: () => void -}) { - const { t: tPlugin } = useTranslation('plugin') - const { t: tWorkflow } = useTranslation('workflow') - - return ( -
- - {icon} - -

{provider}

-

- {tWorkflow(($) => $['nodes.common.pluginNotInstalled'])} -

- -
- ) -} diff --git a/web/features/new-rag/sources/setup/provider-options.ts b/web/features/new-rag/sources/setup/provider-options.ts index a7a7869b230..e82ee13504d 100644 --- a/web/features/new-rag/sources/setup/provider-options.ts +++ b/web/features/new-rag/sources/setup/provider-options.ts @@ -1,5 +1,10 @@ import type { NewKnowledgeSourceDraft, NewKnowledgeSourceType } from './source-draft' import type { DataSourceItem } from '@/app/components/workflow/block-selector/types' +import { + datasourceParameterDefaults, + datasourceParameterSchemas, + websiteDatasourceParameterSchemas, +} from './datasource-parameter-model' type Datasource = DataSourceItem['declaration']['datasources'][number] @@ -15,7 +20,6 @@ type RecommendedProvider = { export type InstalledSourceProviderOption = { datasource: Datasource fallbackIcon: string - installed: true key: string label: string packageId: string @@ -24,17 +28,7 @@ export type InstalledSourceProviderOption = { sourceType: NewKnowledgeSourceType } -export type UninstalledSourceProviderOption = { - fallbackIcon: string - installed: false - key: string - label: string - packageId: string - providerType: DataSourceItem['declaration']['provider_type'] - sourceType: NewKnowledgeSourceType -} - -export type SourceProviderOption = InstalledSourceProviderOption | UninstalledSourceProviderOption +export type SourceProviderOption = InstalledSourceProviderOption const recommendedProviders: RecommendedProvider[] = [ { @@ -185,7 +179,6 @@ function installedRecommendedProvider( return { datasource, fallbackIcon: definition.fallbackIcon, - installed: true, key: providerKey(definition.sourceType, plugin, datasource), label: definition.label, packageId: definition.packageId, @@ -226,23 +219,11 @@ export function discoverSourceProviderOptions( const definitions = recommendedProviders.filter( (definition) => definition.sourceType === sourceType, ) - const recommended: SourceProviderOption[] = definitions.map((definition) => { + const recommended = definitions.flatMap((definition) => { const installed = installedRecommendedProvider(definition, datasourcePlugins) - return ( - installed ?? { - fallbackIcon: definition.fallbackIcon, - installed: false as const, - key: `${sourceType}:marketplace:${definition.packageId}`, - label: definition.label, - packageId: definition.packageId, - providerType: definition.providerType, - sourceType, - } - ) + return installed ? [installed] : [] }) - const consumedKeys = new Set( - recommended.flatMap((option) => (option.installed ? [option.key] : [])), - ) + const consumedKeys = new Set(recommended.map((option) => option.key)) const labels = new Set(recommended.map((option) => option.label)) const discovered: InstalledSourceProviderOption[] = [] @@ -261,7 +242,6 @@ export function discoverSourceProviderOptions( discovered.push({ datasource, fallbackIcon: fallbackIcon(sourceType), - installed: true, key, label, packageId: plugin.plugin_id, @@ -288,3 +268,32 @@ export function sourceProviderOptionForDraft( options[0] ) } + +export function sourceDraftForProviderOption( + draft: T, + option: SourceProviderOption, +): T { + const providerMatches = + draft.providerKey !== undefined + ? draft.providerKey === option.key + : normalizeSourceProviderName(draft.provider) === normalizeSourceProviderName(option.label) + if (providerMatches) + return draft.provider === option.label && draft.providerKey === option.key + ? draft + : ({ ...draft, provider: option.label, providerKey: option.key } as T) + + const schemas = + draft.sourceType === 'websiteCrawl' + ? websiteDatasourceParameterSchemas(option.datasource) + : datasourceParameterSchemas(option.datasource) + return { + ...draft, + parameters: datasourceParameterDefaults(schemas), + provider: option.label, + providerKey: option.key, + sourceName: '', + ...(draft.sourceType === 'websiteCrawl' + ? { includeSubpages: true, maxPages: 100, rootUrl: '' } + : {}), + } as T +} diff --git a/web/features/new-rag/sources/source-edit-dialog.tsx b/web/features/new-rag/sources/source-edit-dialog.tsx index 582f8b45609..9deddc7cd94 100644 --- a/web/features/new-rag/sources/source-edit-dialog.tsx +++ b/web/features/new-rag/sources/source-edit-dialog.tsx @@ -224,7 +224,7 @@ function ConnectedSourceEditDialogContent({ const providerOption = providerOptions.find( (option) => normalizeSourceProviderName(option.label) === normalizedProviderName, ) - const installedProviderOption = providerOption?.installed ? providerOption : undefined + const installedProviderOption = providerOption const connections = connectionsData?.pages.flatMap((page) => sourceConnectionListFromApi(page).items) ?? [] const connection = connections.find((item) => item.id === source.connectionId) @@ -516,13 +516,10 @@ function WebsiteSourceEditDialogContent({ const providerLoading = usesProviderDeclaration && datasourcePluginsQuery.isPending const providerLoadFailed = usesProviderDeclaration && datasourcePluginsQuery.isError const providerConfigurationReady = - !usesProviderDeclaration || - (!providerLoading && !providerLoadFailed && providerOption?.installed === true) + !usesProviderDeclaration || (!providerLoading && !providerLoadFailed && Boolean(providerOption)) const parameterSchemas = useMemo(() => { if (!usesProviderDeclaration) return websiteDatasourceParameterSchemas() - return providerOption?.installed - ? websiteDatasourceParameterSchemas(providerOption.datasource) - : [] + return providerOption ? websiteDatasourceParameterSchemas(providerOption.datasource) : [] }, [providerOption, usesProviderDeclaration]) const displayedParameters = useMemo( () => sourceParametersForSchemas(initialSource, nextParameters, parameterSchemas), @@ -577,7 +574,7 @@ function WebsiteSourceEditDialogContent({ } const startPreview = async () => { - if (previewing || !parametersValid || !providerOption?.installed || !previewBindingReady) return + if (previewing || !parametersValid || !providerOption || !previewBindingReady) return const attempt = previewAttemptRef.current + 1 previewAttemptRef.current = attempt setPreviewing(true) @@ -735,7 +732,7 @@ function WebsiteSourceEditDialogContent({ pending || previewing || !parametersValid || - !providerOption?.installed || + !providerOption || !previewBindingReady } loading={previewing}