[Search] Add connectors table (#167900)

## Summary

This adds a connectors table to the Serverless Search plugin. Actions
for the table will be added after [this related
PR](https://github.com/elastic/kibana/pull/167804) has merged.
<img width="1318" alt="Screenshot 2023-10-03 at 17 24 22"
src="62383a52-e7fd-4458-8db9-d6e433f3f94c">
This commit is contained in:
Sander Philipse 2023-10-04 16:35:27 +02:00 committed by GitHub
parent 9e304027e5
commit ad0c38d75d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 510 additions and 249 deletions

View file

@ -7,3 +7,4 @@
*/
export * from './is_category_entry';
export * from './sync_status_to_text';

View file

@ -1,11 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SyncStatus } from '@kbn/search-connectors';
import { SyncStatus } from '..';
import { syncStatusToColor, syncStatusToText } from './sync_status_to_text';

View file

@ -1,44 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { SyncJobType, SyncStatus } from '@kbn/search-connectors';
import { SyncJobType, SyncStatus } from '..';
export function syncStatusToText(status: SyncStatus): string {
switch (status) {
case SyncStatus.COMPLETED:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.completed', {
return i18n.translate('searchConnectors.syncStatus.completed', {
defaultMessage: 'Sync complete',
});
case SyncStatus.ERROR:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.error', {
return i18n.translate('searchConnectors.syncStatus.error', {
defaultMessage: 'Sync failure',
});
case SyncStatus.IN_PROGRESS:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.inProgress', {
return i18n.translate('searchConnectors.syncStatus.inProgress', {
defaultMessage: 'Sync in progress',
});
case SyncStatus.CANCELED:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.canceling', {
return i18n.translate('searchConnectors.syncStatus.canceling', {
defaultMessage: 'Sync canceled',
});
case SyncStatus.CANCELING:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.canceled', {
return i18n.translate('searchConnectors.syncStatus.canceled', {
defaultMessage: 'Canceling sync',
});
case SyncStatus.PENDING:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.pending', {
return i18n.translate('searchConnectors.syncStatus.pending', {
defaultMessage: 'Sync pending',
});
case SyncStatus.SUSPENDED:
return i18n.translate('xpack.enterpriseSearch.content.syncStatus.suspended', {
return i18n.translate('searchConnectors.syncStatus.suspended', {
defaultMessage: 'Sync suspended',
});
default:
return status;
}
}
@ -54,17 +57,19 @@ export function syncStatusToColor(status: SyncStatus): string {
case SyncStatus.SUSPENDED:
case SyncStatus.CANCELING:
return 'warning';
default:
return 'default';
}
}
export const syncJobTypeToText = (syncType: SyncJobType): string => {
switch (syncType) {
case SyncJobType.FULL:
return i18n.translate('xpack.enterpriseSearch.content.syncJobType.full', {
return i18n.translate('searchConnectors.syncJobType.full', {
defaultMessage: 'Full content',
});
case SyncJobType.INCREMENTAL:
return i18n.translate('xpack.enterpriseSearch.content.syncJobType.incremental', {
return i18n.translate('searchConnectors.syncJobType.incremental', {
defaultMessage: 'Incremental content',
});
default:

View file

@ -15,15 +15,12 @@ import { i18n } from '@kbn/i18n';
import { SyncJobType, SyncStatus } from '@kbn/search-connectors';
import { syncJobTypeToText, syncStatusToColor, syncStatusToText } from '@kbn/search-connectors';
import { FormattedDateTime } from '../../../../shared/formatted_date_time';
import { pageToPagination } from '../../../../shared/pagination/page_to_pagination';
import { durationToText } from '../../../utils/duration_to_text';
import {
syncJobTypeToText,
syncStatusToColor,
syncStatusToText,
} from '../../../utils/sync_status_to_text';
import { IndexViewLogic } from '../index_view_logic';

View file

@ -54,6 +54,9 @@ export const INVALID_JSON_ERROR: string = i18n.translate(
}
);
export const CONNECTORS_LABEL: string = i18n.translate('xpack.serverlessSearch.connectors', {
defaultMessage: 'Connectors',
});
export const CONNECTOR_LABEL: string = i18n.translate('xpack.serverlessSearch.connector', {
defaultMessage: 'Connector',
});

View file

@ -0,0 +1,234 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
Criteria,
EuiBadge,
EuiBasicTable,
EuiBasicTableColumn,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiI18nNumber,
EuiIcon,
EuiLink,
EuiSearchBar,
EuiSelect,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
Connector,
ConnectorStatus,
SyncStatus,
syncStatusToColor,
syncStatusToText,
} from '@kbn/search-connectors';
import React, { useEffect, useState } from 'react';
import { generatePath } from 'react-router-dom';
import { CONNECTORS_LABEL } from '../../../../common/i18n_string';
import { useConnectors } from '../../hooks/api/use_connectors';
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
import { EDIT_CONNECTOR_PATH } from '../connectors_router';
export const ConnectorsTable: React.FC = () => {
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const [query, setQuery] = useState<string>('');
const { data, isError } = useConnectors();
const { data: connectorTypes } = useConnectorTypes();
type Filter = 'service_type' | 'name' | 'last_sync_status' | 'description' | 'service_type';
const [filter, setFilter] = useState<Filter>('name');
useEffect(() => {
if (query) {
setPageIndex(0);
}
}, [query, filter]);
if (isError) {
return (
<EuiEmptyPrompt>
{i18n.translate('xpack.serverlessSearch.connectors.errorFetchingConnectors', {
defaultMessage: 'We encountered an error fetching your connectors.',
})}
</EuiEmptyPrompt>
);
}
const connectedLabel = i18n.translate('xpack.serverlessSearch.connectors.connected', {
defaultMessage: 'Connected',
});
const configuredLabel = i18n.translate('xpack.serverlessSearch.connectors.configuredLabel', {
defaultMessage: 'Configured',
});
const typeLabel = i18n.translate('xpack.serverlessSearch.connectors.typeLabel', {
defaultMessage: 'Type',
});
const nameLabel = i18n.translate('xpack.serverlessSearch.connectors.nameLabel', {
defaultMessage: 'Name',
});
const syncStatusLabel = i18n.translate('xpack.serverlessSearch.connectors.syncStatusLabel', {
defaultMessage: 'Sync status',
});
const filterOptions: Array<{ text: string; value: Filter }> = [
{ text: nameLabel, value: 'name' },
{ text: typeLabel, value: 'service_type' },
{
text: i18n.translate('xpack.serverlessSearch.connectors.descriptionLabel', {
defaultMessage: 'Description',
}),
value: 'description',
},
{ text: syncStatusLabel, value: 'last_sync_status' },
];
const columns: Array<EuiBasicTableColumn<Connector>> = [
{
field: 'name',
name: nameLabel,
render: (name: string, connector: Connector) => (
<EuiLink href={generatePath(EDIT_CONNECTOR_PATH, { id: connector.id })}>
{name || connector.id}
</EuiLink>
),
truncateText: true,
},
{
field: 'service_type',
name: typeLabel,
render: (serviceType: string | null) => {
const typeData = (connectorTypes?.connectors || []).find(
(connector) => connector.serviceType === (serviceType ?? '')
);
if (!typeData) {
return <></>;
}
return (
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type={typeData.iconPath} aria-label={typeData.name} />
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s">{typeData.name}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
},
},
{
field: 'status',
name: connectedLabel,
render: (status: ConnectorStatus) =>
status === ConnectorStatus.CONNECTED ? (
<EuiIcon aria-label={connectedLabel} color="success" type="checkInCircleFilled" />
) : (
<EuiBadge>
{i18n.translate('xpack.serverlessSearch.connectors.notConnectedLabel', {
defaultMessage: 'Not connected',
})}
</EuiBadge>
),
},
{
field: 'status',
name: configuredLabel,
render: (status: ConnectorStatus) =>
[ConnectorStatus.CONNECTED, ConnectorStatus.CONFIGURED].includes(status) ? (
<EuiIcon aria-label={configuredLabel} color="success" type="checkInCircleFilled" />
) : (
<EuiBadge>
{i18n.translate('xpack.serverlessSearch.connectors.notConfiguredLabel', {
defaultMessage: 'Not configured',
})}
</EuiBadge>
),
},
{
field: 'last_sync_status',
name: syncStatusLabel,
render: (syncStatus: SyncStatus | null) =>
syncStatus ? (
<EuiBadge color={syncStatusToColor(syncStatus)}>{syncStatusToText(syncStatus)}</EuiBadge>
) : (
<EuiBadge>
{i18n.translate('xpack.serverlessSearch.connectors.notSyncedLabel', {
defaultMessage: 'Not synced',
})}
</EuiBadge>
),
},
];
const items =
data?.connectors
.filter((connector) =>
filter ? `${connector[filter]}`.toLowerCase().includes(query.toLowerCase()) : true
)
.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize) ?? [];
return (
<>
<EuiFlexGroup direction="row">
<EuiFlexItem>
<EuiSearchBar onChange={({ queryText }) => setQuery(queryText ?? '')} query={query} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSelect
onChange={(e) => setFilter(e.currentTarget.value as Filter)}
options={filterOptions}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xl" />
<EuiText size="xs">
<FormattedMessage
id="xpack.serverlessSearch.connectorsTable.summaryLabel"
defaultMessage="Showing {items} of {count} {connectors}"
values={{
connectors: <strong>{CONNECTORS_LABEL}</strong>,
items: (
<strong>
<EuiI18nNumber value={(pageIndex + 1) * pageSize} />-
<EuiI18nNumber value={(pageIndex + 2) * pageSize} />
</strong>
),
count: <EuiI18nNumber value={items.length} />,
}}
/>
</EuiText>
<EuiSpacer size="s" />
<EuiHorizontalRule margin="none" style={{ height: 2 }} />
<EuiBasicTable
columns={columns}
items={items}
onChange={({ page }: Criteria<Connector>) => {
if (page) {
const { index, size } = page;
setPageIndex(index);
setPageSize(size);
}
}}
pagination={{
pageIndex,
pageSize,
totalItemCount: data?.connectors.length ?? 0,
}}
/>
</>
);
};

View file

@ -42,7 +42,7 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
const { isLoading, isSuccess, mutate } = useMutation({
mutationFn: async (inputDescription: string) => {
const body = { Description: inputDescription };
const body = { description: inputDescription };
const result = await http.post(
`/internal/serverless_search/connectors/${connectorId}/description`,
{
@ -83,7 +83,7 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
value={newDescription}
/>
) : (
<EuiText>{description}</EuiText>
<EuiText size="s">{description}</EuiText>
)}
</EuiFormRow>
</EuiFlexItem>

View file

@ -0,0 +1,196 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiIcon,
EuiTitle,
EuiText,
EuiLink,
EuiButton,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { PLUGIN_ID } from '../../../../common';
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
import { useKibanaServices } from '../../hooks/use_kibana';
export const EmptyConnectorsPrompt: React.FC = () => {
const { http } = useKibanaServices();
const { data: connectorTypes } = useConnectorTypes();
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
const connectorsPath = assetBasePath + '/connectors.svg';
return (
<EuiFlexGroup alignItems="center" direction="column">
<EuiFlexItem>
<EuiPanel paddingSize="l" hasShadow={false} hasBorder>
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
<EuiFlexItem>
<EuiIcon size="xxl" type={connectorsPath} />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
<h2>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.title', {
defaultMessage: 'Create a connector',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.description', {
defaultMessage:
"To set up and deploy a connector you'll be working between the third-party data source, your terminal, and the Kibana UI. The high level process looks like this:",
})}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel color="subdued">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column">
<EuiFlexItem grow={false}>
<EuiIcon color="primary" size="l" type="documents" />
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.guideOneDescription',
{
defaultMessage: "Choose a data source you'd like to sync",
}
)}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column">
<EuiFlexItem grow={false}>
<EuiIcon color="primary" size="l" type={connectorsPath} />
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
<FormattedMessage
id="xpack.serverlessSearch.connectorsEmpty.guideTwoDescription"
defaultMessage="Deploy connector code on your own infrastructure by running from {source}, or using {docker}"
values={{
source: (
<EuiLink href="TODO TODO TODO">
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.sourceLabel',
{ defaultMessage: 'source' }
)}
</EuiLink>
),
docker: (
<EuiLink href="TODO TODO TODO">
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.dockerLabel',
{ defaultMessage: 'Docker' }
)}
</EuiLink>
),
}}
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column">
<EuiFlexItem grow={false}>
<EuiFlexGroup
gutterSize="s"
direction="row"
alignItems="center"
justifyContent="center"
>
<EuiFlexItem>
<EuiIcon color="primary" size="l" type="documents" />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon size="m" type="sortRight" />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon color="primary" size="l" type={connectorsPath} />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon size="m" type="sortRight" />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon color="primary" size="l" type="logoElastic" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.guideThreeDescription',
{
defaultMessage:
'Enter access and connection details for your data source and run your first sync',
}
)}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiButton fill iconType="plusInCircleFilled">
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', {
defaultMessage: 'Create connector',
})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
<h3>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.availableConnectors', {
defaultMessage: 'Available connectors',
})}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s">
{connectorTypes?.connectors.map((connectorType) => (
<EuiFlexItem key={connectorType.name}>
<EuiToolTip content={connectorType.name}>
<EuiIcon
size="l"
title={connectorType.name}
id={connectorType.serviceType}
type={connectorType.iconPath}
/>
</EuiToolTip>
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -12,40 +12,29 @@ import {
EuiIcon,
EuiLink,
EuiPageTemplate,
EuiPanel,
EuiText,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import { Connector } from '@kbn/search-connectors';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import React, { useEffect } from 'react';
import { LEARN_MORE_LABEL } from '../../../common/i18n_string';
import { PLUGIN_ID } from '../../../common';
import { useConnectors } from '../hooks/api/use_connectors';
import { useKibanaServices } from '../hooks/use_kibana';
import { EmptyConnectorsPrompt } from './connectors/empty_connectors_prompt';
import { ConnectorsTable } from './connectors/connectors_table';
import { CREATE_CONNECTOR_PATH } from './connectors_router';
import { useConnectorTypes } from '../hooks/api/use_connector_types';
export const ConnectorsOverview = () => {
const { data } = useConnectors();
const {
application: { navigateToUrl },
http,
} = useKibanaServices();
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
const connectorsPath = assetBasePath + '/connectors.svg';
const { data } = useQuery({
queryKey: ['fetchConnectors'],
queryFn: () =>
http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors'),
});
const { data: connectorTypes } = useConnectorTypes();
const {
data: connector,
isLoading,
@ -61,7 +50,9 @@ export const ConnectorsOverview = () => {
});
useEffect(() => {
navigateToUrl(`${CREATE_CONNECTOR_PATH}/${connector?.id}`);
if (isSuccess) {
navigateToUrl(`${CREATE_CONNECTOR_PATH}/${connector?.id}`);
}
}, [connector, isSuccess, navigateToUrl]);
const createConnector = () => mutate();
@ -131,189 +122,12 @@ export const ConnectorsOverview = () => {
</EuiText>
</EuiPageTemplate.Header>
{(data?.connectors || []).length > 0 ? (
<></>
<EuiPageTemplate.Section restrictWidth color="subdued">
<ConnectorsTable />
</EuiPageTemplate.Section>
) : (
<EuiPageTemplate.Section grow restrictWidth color="subdued">
<EuiFlexGroup alignItems="center" direction="column">
<EuiFlexItem>
<EuiPanel paddingSize="l" hasShadow={false} hasBorder>
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
<EuiFlexItem>
<EuiIcon size="xxl" type={connectorsPath} />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
<h2>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.title', {
defaultMessage: 'Create a connector',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.description', {
defaultMessage:
"To set up and deploy a connector you'll be working between the third-party data source, your terminal, and the Kibana UI. The high level process looks like this:",
})}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel color="subdued">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup
justifyContent="center"
alignItems="center"
direction="column"
>
<EuiFlexItem grow={false}>
<EuiIcon color="primary" size="l" type="documents" />
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.guideOneDescription',
{
defaultMessage: "Choose a data source you'd like to sync",
}
)}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup
justifyContent="center"
alignItems="center"
direction="column"
>
<EuiFlexItem grow={false}>
<EuiIcon color="primary" size="l" type={connectorsPath} />
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
<FormattedMessage
id="xpack.serverlessSearch.connectorsEmpty.guideTwoDescription"
defaultMessage="Deploy connector code on your own infrastructure by running from {source}, or using {docker}"
values={{
source: (
<EuiLink href="TODO TODO TODO">
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.sourceLabel',
{ defaultMessage: 'source' }
)}
</EuiLink>
),
docker: (
<EuiLink href="TODO TODO TODO">
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.dockerLabel',
{ defaultMessage: 'Docker' }
)}
</EuiLink>
),
}}
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup
justifyContent="center"
alignItems="center"
direction="column"
>
<EuiFlexItem grow={false}>
<EuiFlexGroup
gutterSize="s"
direction="row"
alignItems="center"
justifyContent="center"
>
<EuiFlexItem>
<EuiIcon color="primary" size="l" type="documents" />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon size="m" type="sortRight" />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon color="primary" size="l" type={connectorsPath} />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon size="m" type="sortRight" />
</EuiFlexItem>
<EuiFlexItem>
<EuiIcon color="primary" size="l" type="logoElastic" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.guideThreeDescription',
{
defaultMessage:
'Enter access and connection details for your data source and run your first sync',
}
)}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiButton
isLoading={isLoading}
fill
iconType="plusInCircleFilled"
onClick={() => createConnector()}
>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', {
defaultMessage: 'Create connector',
})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
<h3>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.availableConnectors', {
defaultMessage: 'Available connectors',
})}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s">
{connectorTypes?.connectors.map((connectorType) => (
<EuiFlexItem key={connectorType.name}>
<EuiToolTip content={connectorType.name}>
<EuiIcon
size="l"
title={connectorType.name}
id={connectorType.serviceType}
type={connectorType.iconPath}
/>
</EuiToolTip>
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPageTemplate.Section restrictWidth color="subdued">
<EmptyConnectorsPrompt />
</EuiPageTemplate.Section>
)}
</EuiPageTemplate>

View file

@ -12,7 +12,8 @@ import { ConnectorsOverview } from './connectors_overview';
export const BASE_CONNECTORS_PATH = 'connectors';
export const CREATE_CONNECTOR_SLUG = `create_connector`;
export const CREATE_CONNECTOR_PATH = `${BASE_CONNECTORS_PATH}/${CREATE_CONNECTOR_SLUG}`;
export const EDIT_CONNECTOR_PATH = `${CREATE_CONNECTOR_SLUG}/:id`;
export const CREATE_CONNECTOR_PATH = `${CREATE_CONNECTOR_SLUG}`;
export const ConnectorsRouter: React.FC = () => {
return (

View file

@ -11,6 +11,7 @@ import { useKibanaServices } from '../use_kibana';
export const useConnectorTypes = () => {
const { http } = useKibanaServices();
return useQuery({
queryKey: ['fetchConnectorTypes'],
queryFn: () =>

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Connector } from '@kbn/search-connectors';
import { useQuery } from '@tanstack/react-query';
import { useKibanaServices } from '../use_kibana';
export const useConnectors = (from: number = 0, size: number = 10) => {
const { http } = useKibanaServices();
return useQuery({
queryKey: ['fetchConnectors', from, size],
queryFn: () =>
http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors', {
query: { from, size },
}),
});
};

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PLUGIN_ID } from '../../../common';
import { useKibanaServices } from './use_kibana';
export const useAssetBasePath = () => {
const { http } = useKibanaServices();
return http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
};

View file

@ -13818,15 +13818,6 @@
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "Sélectionnez le type de tâche de synchronisation à afficher.",
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "Type de tâche de synchronisation",
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "Durée de synchronisation",
"xpack.enterpriseSearch.content.syncJobType.full": "Contenu entier",
"xpack.enterpriseSearch.content.syncJobType.incremental": "Contenu progressif",
"xpack.enterpriseSearch.content.syncStatus.canceled": "Annulation de la synchronisation",
"xpack.enterpriseSearch.content.syncStatus.canceling": "Synchronisation annulée",
"xpack.enterpriseSearch.content.syncStatus.completed": "Synchronisation terminée",
"xpack.enterpriseSearch.content.syncStatus.error": "Échec de la synchronisation",
"xpack.enterpriseSearch.content.syncStatus.inProgress": "Synchronisation en cours",
"xpack.enterpriseSearch.content.syncStatus.pending": "Synchronisation en attente",
"xpack.enterpriseSearch.content.syncStatus.suspended": "Synchronisation suspendue",
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "Vous pouvez ajouter plusieurs domaines au robot d'indexation de cet index. Ajoutez un autre domaine ici et modifiez les points d'entrée et les règles d'indexation à partir de la page \"Gérer\".",
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "Ajouter un domaine",
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "Ajouter un nouveau domaine",

View file

@ -13832,15 +13832,6 @@
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "表示する同期ジョブタイプを選択します。",
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "同期ジョブタイプ",
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "同期時間",
"xpack.enterpriseSearch.content.syncJobType.full": "完全なコンテンツ",
"xpack.enterpriseSearch.content.syncJobType.incremental": "増分コンテンツ",
"xpack.enterpriseSearch.content.syncStatus.canceled": "同期のキャンセル中",
"xpack.enterpriseSearch.content.syncStatus.canceling": "同期がキャンセルされました",
"xpack.enterpriseSearch.content.syncStatus.completed": "同期完了",
"xpack.enterpriseSearch.content.syncStatus.error": "同期失敗",
"xpack.enterpriseSearch.content.syncStatus.inProgress": "同期は実行中です",
"xpack.enterpriseSearch.content.syncStatus.pending": "同期は保留中です",
"xpack.enterpriseSearch.content.syncStatus.suspended": "同期が一時停止されました",
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "複数のドメインをこのインデックスのWebクローラーに追加できます。ここで別のドメインを追加して、[管理]ページからエントリポイントとクロールルールを変更します。",
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "ドメインを追加",
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "新しいドメインを追加",

View file

@ -13832,15 +13832,6 @@
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "选择要显示的同步作业类型。",
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "同步作业类型",
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "同步持续时间",
"xpack.enterpriseSearch.content.syncJobType.full": "完整内容",
"xpack.enterpriseSearch.content.syncJobType.incremental": "增量同步",
"xpack.enterpriseSearch.content.syncStatus.canceled": "正在取消同步",
"xpack.enterpriseSearch.content.syncStatus.canceling": "同步已取消",
"xpack.enterpriseSearch.content.syncStatus.completed": "同步已完成",
"xpack.enterpriseSearch.content.syncStatus.error": "同步失败",
"xpack.enterpriseSearch.content.syncStatus.inProgress": "同步进行中",
"xpack.enterpriseSearch.content.syncStatus.pending": "同步待处理",
"xpack.enterpriseSearch.content.syncStatus.suspended": "同步已挂起",
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "可以将多个域添加到此索引的网络爬虫。在此添加其他域并从“管理”页面修改入口点和爬网规则。",
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "添加域",
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "添加新域",