diff --git a/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap index 2aa23faf3c..e3615782a8 100644 --- a/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap +++ b/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap @@ -42,6 +42,11 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = "type": "", } } + setupCallout={ + Object { + "show": false, + } + } updateConfig={[Function]} > @@ -60,6 +65,11 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = className="euiSpacer euiSpacer--l" /> + +
+
@@ -707,11 +718,11 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = class="euiFlexItem euiFlexItem--flexGrowZero" > - - + + + +
@@ -1045,6 +1046,11 @@ exports[`Integration Setup Page Renders the form as expected 1`] = ` className="euiSpacer euiSpacer--l" /> + +
+
{ config={TEST_INTEGRATION_SETUP_INPUTS} updateConfig={() => {}} integration={TEST_INTEGRATION_CONFIG} + setupCallout={{ show: false }} /> ); diff --git a/public/components/integrations/components/available_integration_overview_page.tsx b/public/components/integrations/components/available_integration_overview_page.tsx index 360bf0db49..cd9a8300b5 100644 --- a/public/components/integrations/components/available_integration_overview_page.tsx +++ b/public/components/integrations/components/available_integration_overview_page.tsx @@ -32,9 +32,9 @@ export interface AvailableIntegrationType { version?: string | undefined; displayName?: string; integrationType: string; - statics: any; - components: any[]; - displayAssets: any[]; + statics: unknown; + components: Array<{ name: string }>; + displayAssets: unknown[]; } export interface AvailableIntegrationsTableProps { @@ -183,7 +183,11 @@ export function AvailableIntegrationOverviewPage(props: AvailableIntegrationOver ? AvailableIntegrationsCardView({ data: { hits: data.hits.filter((hit) => - helper.every((compon) => hit.components.map((x) => x.name).includes(compon)) + helper.every((compon) => + hit.components + .map((x) => x.name.split('_').findLast(() => true)) + .includes(compon) + ) ), }, isCardView, @@ -197,7 +201,11 @@ export function AvailableIntegrationOverviewPage(props: AvailableIntegrationOver loading: false, data: { hits: data.hits.filter((hit) => - helper.every((compon) => hit.components.map((x) => x.name).includes(compon)) + helper.every((compon) => + hit.components + .map((x) => x.name.split('_').findLast(() => true)) + .includes(compon) + ) ), }, isCardView, diff --git a/public/components/integrations/components/create_integration_helpers.ts b/public/components/integrations/components/create_integration_helpers.ts index 7e465c26b4..1bebd558fb 100644 --- a/public/components/integrations/components/create_integration_helpers.ts +++ b/public/components/integrations/components/create_integration_helpers.ts @@ -313,16 +313,13 @@ export async function addIntegrationRequest( .post(`${INTEGRATIONS_BASE}/store/${templateName}`, { body: JSON.stringify({ name, dataSource }), }) - .then((_res) => { + .then((res) => { setToast(`${name} integration successfully added!`, 'success'); - window.location.hash = `#/installed/${_res.data?.id}`; + window.location.hash = `#/installed/${res.data?.id}`; return true; }) - .catch((_err) => { - setToast( - 'Failed to load integration. Check Added Integrations table for more details', - 'danger' - ); + .catch((err) => { + setToast('Failed to load integration', 'danger', err.message); return false; }); if (!addSample || !response) { @@ -333,7 +330,7 @@ export async function addIntegrationRequest( .then((res) => res.data) .catch((err) => { console.error(err); - setToast('The sample data could not be retrieved', 'danger'); + setToast('Failed to load integration', 'danger', 'The sample data could not be retrieved.'); return { sampleData: [] }; }); const requestBody = diff --git a/public/components/integrations/components/setup_integration.tsx b/public/components/integrations/components/setup_integration.tsx index 11d41be354..bb2956db7e 100644 --- a/public/components/integrations/components/setup_integration.tsx +++ b/public/components/integrations/components/setup_integration.tsx @@ -6,6 +6,8 @@ import { EuiBottomBar, EuiButton, + EuiButtonEmpty, + EuiCallOut, EuiComboBox, EuiFieldText, EuiFlexGroup, @@ -25,9 +27,9 @@ import { EuiTitle, } from '@elastic/eui'; import React, { useState, useEffect } from 'react'; +import { Color } from 'common/constants/integrations'; import { coreRefs } from '../../../framework/core_refs'; import { IntegrationTemplate, addIntegrationRequest } from './create_integration_helpers'; -import { useToast } from '../../../../public/components/common/toast'; import { CONSOLE_PROXY, INTEGRATIONS_BASE } from '../../../../common/constants/shared'; import { DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; @@ -38,10 +40,13 @@ export interface IntegrationSetupInputs { connectionLocation: string; } +type SetupCallout = { show: true; title: string; color?: Color; text?: string } | { show: false }; + interface IntegrationConfigProps { config: IntegrationSetupInputs; updateConfig: (updates: Partial) => void; integration: IntegrationTemplate; + setupCallout: SetupCallout; } // TODO support localization @@ -156,7 +161,10 @@ const runQuery = async ( trackProgress(3); return { ok: true, value: poll }; } else if (poll.status === 'FAILURE') { - return { ok: false, error: new Error('FAILURE status', { cause: poll }) }; + return { + ok: false, + error: new Error(poll.error ?? 'No error information provided', { cause: poll }), + }; } await sleep(3000); } @@ -170,6 +178,7 @@ export function SetupIntegrationForm({ config, updateConfig, integration, + setupCallout, }: IntegrationConfigProps) { const connectionType = INTEGRATION_CONNECTION_DATA_SOURCE_TYPES.get(config.connectionType)!; @@ -194,6 +203,12 @@ export function SetupIntegrationForm({

Set Up Integration

+ {setupCallout.show ? ( + +

{setupCallout.text}

+
+ ) : null} +

Integration Details

@@ -262,6 +277,7 @@ export function SetupBottomBar({ setLoading, loadingProgress, setProgress, + setSetupCallout, }: { config: IntegrationSetupInputs; integration: IntegrationTemplate; @@ -269,15 +285,23 @@ export function SetupBottomBar({ setLoading: (loading: boolean) => void; loadingProgress: number; setProgress: (updater: number | ((progress: number) => number)) => void; + setSetupCallout: (setupCallout: SetupCallout) => void; }) { - const { setToast } = useToast(); + // Drop-in replacement for setToast + const setCalloutLikeToast = (title: string, color?: Color, text?: string) => + setSetupCallout({ + show: true, + title, + color, + text, + }); return ( - { // TODO evil hack because props aren't set up @@ -288,7 +312,7 @@ export function SetupBottomBar({ }} > Discard - + )} @@ -441,6 +471,7 @@ export function SetupIntegrationPage({ integration }: { integration: string }) { setLoading={setShowLoading} loadingProgress={loadingProgress} setProgress={setLoadingProgress} + setSetupCallout={setSetupCallout} /> diff --git a/server/adaptors/integrations/repository/fs_data_adaptor.ts b/server/adaptors/integrations/repository/fs_data_adaptor.ts index df1c6781af..b6915d5eea 100644 --- a/server/adaptors/integrations/repository/fs_data_adaptor.ts +++ b/server/adaptors/integrations/repository/fs_data_adaptor.ts @@ -48,6 +48,15 @@ function tryParseNDJson(content: string): object[] | null { } } +// Check if a location is a directory without an exception if location not found +const safeIsDirectory = async (maybeDirectory: string): Promise => { + try { + return (await fs.lstat(maybeDirectory)).isDirectory(); + } catch (_err: unknown) { + return false; + } +}; + /** * A CatalogDataAdaptor that reads from the local filesystem. * Used to read Integration information when the user uploads their own catalog. @@ -133,15 +142,13 @@ export class FileSystemCatalogDataAdaptor implements CatalogDataAdaptor { } async getDirectoryType(dirname?: string): Promise<'integration' | 'repository' | 'unknown'> { - const isDir = (await fs.lstat(path.join(this.directory, dirname ?? '.'))).isDirectory(); + const isDir = await safeIsDirectory(path.join(this.directory, dirname ?? '.')); if (!isDir) { return 'unknown'; } // Sloppily just check for one mandatory integration directory to distinguish. // Improve if we need to distinguish a repository with an integration named "schemas". - const hasSchemas = ( - await fs.lstat(path.join(this.directory, dirname ?? '.', 'schemas')) - ).isDirectory(); + const hasSchemas = await safeIsDirectory(path.join(this.directory, dirname ?? '.', 'schemas')); return hasSchemas ? 'integration' : 'repository'; }