mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.13`: - [[Search] Fix connectors pages breaking if index has been deleted (#178147)](https://github.com/elastic/kibana/pull/178147) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Sander Philipse","email":"94373878+sphilipse@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-03-07T13:14:45Z","message":"[Search] Fix connectors pages breaking if index has been deleted (#178147)\n\n## Summary\r\n\r\nThis fixes the new connector pages to work even if the attached index\r\nhas been deleted. Mostly does this by heavily relying on\r\nConnectorViewLogic instead of IndexViewLogic.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0dc0d3d6c49374065bc83832d4ec47515f2c727f","branchLabelMapping":{"^v8.14.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:EnterpriseSearch","v8.13.0","v8.14.0"],"number":178147,"url":"https://github.com/elastic/kibana/pull/178147","mergeCommit":{"message":"[Search] Fix connectors pages breaking if index has been deleted (#178147)\n\n## Summary\r\n\r\nThis fixes the new connector pages to work even if the attached index\r\nhas been deleted. Mostly does this by heavily relying on\r\nConnectorViewLogic instead of IndexViewLogic.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0dc0d3d6c49374065bc83832d4ec47515f2c727f"}},"sourceBranch":"main","suggestedTargetBranches":["8.13"],"targetPullRequestStates":[{"branch":"8.13","label":"v8.13.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.14.0","labelRegex":"^v8.14.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/178147","number":178147,"mergeCommit":{"message":"[Search] Fix connectors pages breaking if index has been deleted (#178147)\n\n## Summary\r\n\r\nThis fixes the new connector pages to work even if the attached index\r\nhas been deleted. Mostly does this by heavily relying on\r\nConnectorViewLogic instead of IndexViewLogic.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"0dc0d3d6c49374065bc83832d4ec47515f2c727f"}}]}] BACKPORT-->
This commit is contained in:
parent
017008011d
commit
57485255f9
21 changed files with 404 additions and 277 deletions
|
@ -21,6 +21,7 @@ export interface FetchConnectorsApiLogicArgs {
|
|||
export interface FetchConnectorsApiLogicResponse {
|
||||
connectors: Connector[];
|
||||
counts: Record<string, number>;
|
||||
indexExists: Record<string, boolean>;
|
||||
isInitialRequest: boolean;
|
||||
meta: Meta;
|
||||
}
|
||||
|
|
|
@ -24,14 +24,13 @@ describe('updateConnectorConfigurationLogic', () => {
|
|||
const result = postConnectorConfiguration({
|
||||
configuration,
|
||||
connectorId: 'anIndexId',
|
||||
indexName: 'anIndexName',
|
||||
});
|
||||
await nextTick();
|
||||
expect(http.post).toHaveBeenCalledWith(
|
||||
'/internal/enterprise_search/connectors/anIndexId/configuration',
|
||||
{ body: JSON.stringify(configuration) }
|
||||
);
|
||||
await expect(result).resolves.toEqual({ configuration: 'result', indexName: 'anIndexName' });
|
||||
await expect(result).resolves.toEqual({ configuration: 'result' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,25 +15,22 @@ import { HttpLogic } from '../../../shared/http';
|
|||
export interface PostConnectorConfigurationArgs {
|
||||
configuration: Record<string, string | number | boolean | null>;
|
||||
connectorId: string;
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export interface PostConnectorConfigurationResponse {
|
||||
configuration: ConnectorConfiguration;
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export const postConnectorConfiguration = async ({
|
||||
configuration,
|
||||
connectorId,
|
||||
indexName,
|
||||
}: PostConnectorConfigurationArgs) => {
|
||||
const route = `/internal/enterprise_search/connectors/${connectorId}/configuration`;
|
||||
|
||||
const responseConfig = await HttpLogic.values.http.post<ConnectorConfiguration>(route, {
|
||||
body: JSON.stringify(configuration),
|
||||
});
|
||||
return { configuration: responseConfig, indexName };
|
||||
return { configuration: responseConfig };
|
||||
};
|
||||
|
||||
export const ConnectorConfigurationApiLogic = createApiLogic(
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
|
@ -81,8 +83,21 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
|
|||
}
|
||||
}, [connector.id]);
|
||||
|
||||
const { hash } = useLocation();
|
||||
useEffect(() => {
|
||||
if (hash) {
|
||||
const id = hash.replace('#', '');
|
||||
if (id === 'attachIndexBox') {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [hash]);
|
||||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder>
|
||||
<EuiPanel hasShadow={false} hasBorder id="attachIndexBox">
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
{i18n.translate('xpack.enterpriseSearch.attachIndexBox.h4.attachAnIndexLabel', {
|
||||
|
|
|
@ -54,18 +54,17 @@ import { NativeConnectorConfiguration } from './native_connector_configuration';
|
|||
|
||||
export const ConnectorConfiguration: React.FC = () => {
|
||||
const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic);
|
||||
const { fetchConnector } = useActions(ConnectorViewLogic);
|
||||
const { index, isLoading, connector } = useValues(ConnectorViewLogic);
|
||||
const cloudContext = useCloudDetails();
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { status } = useValues(ConnectorConfigurationApiLogic);
|
||||
const { makeRequest } = useActions(ConnectorConfigurationApiLogic);
|
||||
const { http } = useValues(HttpLogic);
|
||||
const { fetchConnector } = useActions(ConnectorViewLogic);
|
||||
|
||||
if (!connector) {
|
||||
return <></>;
|
||||
}
|
||||
const indexName = connector.index_name ?? '';
|
||||
|
||||
// TODO make it work without index if possible
|
||||
if (connector.is_native && connector.service_type) {
|
||||
|
@ -90,12 +89,19 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
<EuiSteps
|
||||
steps={[
|
||||
{
|
||||
children: (
|
||||
children: connector.index_name ? (
|
||||
<ApiKeyConfig
|
||||
indexName={indexName}
|
||||
indexName={connector.index_name}
|
||||
hasApiKey={!!connector.api_key_id}
|
||||
isNative={false}
|
||||
/>
|
||||
) : (
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectorDetail.configuration.apiKey.noApiKeyLabel',
|
||||
{
|
||||
defaultMessage: 'Please set an index name before generating an API key',
|
||||
}
|
||||
)
|
||||
),
|
||||
status: hasApiKey ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
|
@ -191,14 +197,10 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
connector={connector}
|
||||
hasPlatinumLicense={hasPlatinumLicense}
|
||||
isLoading={status === Status.LOADING}
|
||||
saveConfig={(
|
||||
configuration // TODO update endpoints
|
||||
) =>
|
||||
index &&
|
||||
saveConfig={(configuration) =>
|
||||
makeRequest({
|
||||
configuration,
|
||||
connectorId: connector.id,
|
||||
indexName: index.name,
|
||||
})
|
||||
}
|
||||
subscriptionLink={docLinks.licenseManagement}
|
||||
|
|
|
@ -123,7 +123,7 @@ export const ConnectorDetail: React.FC = () => {
|
|||
? [
|
||||
{
|
||||
content: <ConnectorSyncRules />,
|
||||
disabled: !index,
|
||||
disabled: !connector?.index_name,
|
||||
id: ConnectorDetailTabId.SYNC_RULES,
|
||||
isSelected: tabId === ConnectorDetailTabId.SYNC_RULES,
|
||||
label: i18n.translate(
|
||||
|
@ -144,7 +144,7 @@ export const ConnectorDetail: React.FC = () => {
|
|||
: []),
|
||||
{
|
||||
content: <ConnectorSchedulingComponent />,
|
||||
disabled: !index,
|
||||
disabled: !connector?.index_name,
|
||||
id: ConnectorDetailTabId.SCHEDULING,
|
||||
isSelected: tabId === ConnectorDetailTabId.SCHEDULING,
|
||||
label: i18n.translate(
|
||||
|
@ -186,7 +186,7 @@ export const ConnectorDetail: React.FC = () => {
|
|||
|
||||
const PIPELINES_TAB = {
|
||||
content: <SearchIndexPipelines />,
|
||||
disabled: !index,
|
||||
disabled: !connector?.index_name,
|
||||
id: ConnectorDetailTabId.PIPELINES,
|
||||
isSelected: tabId === ConnectorDetailTabId.PIPELINES,
|
||||
label: i18n.translate(
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
|
||||
import { CONNECTOR_DETAIL_PATH, CONNECTOR_DETAIL_TAB_PATH } from '../../routes';
|
||||
|
@ -25,6 +27,12 @@ export const ConnectorDetailRouter: React.FC = () => {
|
|||
unmountView();
|
||||
};
|
||||
}, []);
|
||||
const { setIndexName } = useActions(IndexNameLogic);
|
||||
const { connector } = useValues(ConnectorViewLogic);
|
||||
const indexName = connector?.index_name || '';
|
||||
useEffect(() => {
|
||||
setIndexName(indexName);
|
||||
}, [indexName]);
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={CONNECTOR_DETAIL_PATH} exact>
|
||||
|
|
|
@ -20,12 +20,12 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import { Connector, ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { ConnectorIndex } from '../../../../../common/types/indices';
|
||||
import { ElasticsearchIndex } from '../../../../../common/types/indices';
|
||||
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
import { EuiLinkTo } from '../../../shared/react_router_helpers';
|
||||
import { EuiButtonEmptyTo, EuiButtonTo } from '../../../shared/react_router_helpers';
|
||||
import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes';
|
||||
import {
|
||||
connectorStatusToColor,
|
||||
|
@ -38,7 +38,7 @@ import { ConnectorDetailTabId } from './connector_detail';
|
|||
|
||||
export interface ConnectorStatsProps {
|
||||
connector: Connector;
|
||||
indexData?: ConnectorIndex;
|
||||
indexData?: ElasticsearchIndex;
|
||||
}
|
||||
|
||||
export interface StatCardProps {
|
||||
|
@ -47,10 +47,6 @@ export interface StatCardProps {
|
|||
title: string;
|
||||
}
|
||||
|
||||
const noIndexText = i18n.translate('xpack.enterpriseSearch.connectors.connectorStats.noIndex', {
|
||||
defaultMessage: 'No index related',
|
||||
});
|
||||
|
||||
export const StatCard: React.FC<StatCardProps> = ({ title, content, footer }) => {
|
||||
return (
|
||||
<EuiSplitPanel.Outer hasShadow={false} hasBorder grow>
|
||||
|
@ -71,6 +67,27 @@ export const StatCard: React.FC<StatCardProps> = ({ title, content, footer }) =>
|
|||
);
|
||||
};
|
||||
|
||||
const seeDocumentsLabel = i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.seeDocumentsTextLabel',
|
||||
{
|
||||
defaultMessage: 'See documents',
|
||||
}
|
||||
);
|
||||
|
||||
const pipelinesLabel = i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.managePipelines',
|
||||
{
|
||||
defaultMessage: 'Manage pipelines',
|
||||
}
|
||||
);
|
||||
|
||||
const configureLabel = i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.configureLink',
|
||||
{
|
||||
defaultMessage: 'Configure',
|
||||
}
|
||||
);
|
||||
|
||||
export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, indexData }) => {
|
||||
const connectorDefinition = CONNECTORS.find((c) => c.serviceType === connector.service_type);
|
||||
return (
|
||||
|
@ -84,24 +101,27 @@ export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, index
|
|||
}
|
||||
)}
|
||||
content={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
{connectorDefinition && connectorDefinition.icon && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={connectorDefinition.icon} size="xl" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="m" responsive={false} alignItems="center">
|
||||
{connectorDefinition && connectorDefinition.icon && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={connectorDefinition.icon} size="xl" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>{connectorDefinition?.name ?? '-'}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiText>
|
||||
<p>{connectorDefinition?.name ?? '-'}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color={connectorStatusToColor(connector?.status)}>
|
||||
{connectorStatusToText(connector?.status)}
|
||||
<EuiBadge
|
||||
color={connectorStatusToColor(connector?.status, !!connector?.index_name)}
|
||||
>
|
||||
{connectorStatusToText(connector?.status, !!connector?.index_name)}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -109,7 +129,7 @@ export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, index
|
|||
footer={
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="documents" />
|
||||
</EuiFlexItem>
|
||||
|
@ -121,7 +141,7 @@ export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, index
|
|||
{
|
||||
defaultMessage: '{documentAmount} Documents',
|
||||
values: {
|
||||
documentAmount: indexData?.count ?? '-',
|
||||
documentAmount: indexData?.count ?? 0,
|
||||
},
|
||||
}
|
||||
)}
|
||||
|
@ -130,28 +150,17 @@ export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, index
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{connector.index_name ? (
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.DOCUMENTS,
|
||||
})}
|
||||
>
|
||||
<EuiText size="s" textAlign="right">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.seeDocumentsTextLabel',
|
||||
{
|
||||
defaultMessage: 'See documents',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiLinkTo>
|
||||
) : (
|
||||
<EuiText size="s" textAlign="right">
|
||||
{noIndexText}
|
||||
</EuiText>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmptyTo
|
||||
isDisabled={!(connector.index_name && indexData)}
|
||||
size="s"
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.DOCUMENTS,
|
||||
})}
|
||||
>
|
||||
{seeDocumentsLabel}
|
||||
</EuiButtonEmptyTo>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
|
@ -162,36 +171,61 @@ export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, index
|
|||
title="Index"
|
||||
content={
|
||||
connector.index_name ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge>{connector.index_name}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="success" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
indexData ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge>{connector.index_name}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="success" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiText size="s" color="warning">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.indexDoesntExistLabel',
|
||||
{
|
||||
defaultMessage: "Index doesn't exist",
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
)
|
||||
) : (
|
||||
noIndexText
|
||||
<EuiText size="s" color="danger">
|
||||
{i18n.translate('xpack.enterpriseSearch.connectors.connectorStats.noIndexLabel', {
|
||||
defaultMessage: 'No index attached yet',
|
||||
})}
|
||||
</EuiText>
|
||||
)
|
||||
}
|
||||
footer={
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})}
|
||||
>
|
||||
<EuiText textAlign="right" size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.configureLink',
|
||||
{
|
||||
defaultMessage: 'Configure',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiLinkTo>
|
||||
{[ConnectorStatus.CONNECTED, ConnectorStatus.CONFIGURED].includes(
|
||||
connector.status
|
||||
) && connector.index_name ? (
|
||||
<EuiButtonEmptyTo
|
||||
size="s"
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})}
|
||||
>
|
||||
{configureLabel}
|
||||
</EuiButtonEmptyTo>
|
||||
) : (
|
||||
<EuiButtonTo
|
||||
color="primary"
|
||||
size="s"
|
||||
fill
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})}
|
||||
>
|
||||
{configureLabel}
|
||||
</EuiButtonTo>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
|
@ -218,27 +252,16 @@ export const ConnectorStats: React.FC<ConnectorStatsProps> = ({ connector, index
|
|||
footer={
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
{connector.index_name ? (
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.PIPELINES,
|
||||
})}
|
||||
>
|
||||
<EuiText textAlign="right" size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.connectors.connectorStats.managePipelines',
|
||||
{
|
||||
defaultMessage: 'Manage pipelines',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiLinkTo>
|
||||
) : (
|
||||
<EuiText textAlign="right" size="s">
|
||||
{noIndexText}
|
||||
</EuiText>
|
||||
)}
|
||||
<EuiButtonEmptyTo
|
||||
isDisabled={!connector.index_name}
|
||||
size="s"
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.PIPELINES,
|
||||
})}
|
||||
>
|
||||
{pipelinesLabel}
|
||||
</EuiButtonEmptyTo>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
|
|
|
@ -8,18 +8,8 @@
|
|||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { Status } from '../../../../../common/types/api';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
|
||||
import {
|
||||
CachedFetchIndexApiLogic,
|
||||
CachedFetchIndexApiLogicActions,
|
||||
} from '../../api/index/cached_fetch_index_api_logic';
|
||||
|
||||
import { CONNECTORS_PATH } from '../../routes';
|
||||
|
||||
interface OverviewLogicActions {
|
||||
apiError: CachedFetchIndexApiLogicActions['apiError'];
|
||||
}
|
||||
import { CachedFetchIndexApiLogic } from '../../api/index/cached_fetch_index_api_logic';
|
||||
|
||||
interface OverviewLogicValues {
|
||||
apiKey: string;
|
||||
|
@ -30,18 +20,10 @@ interface OverviewLogicValues {
|
|||
status: typeof CachedFetchIndexApiLogic.values.status;
|
||||
}
|
||||
|
||||
export const OverviewLogic = kea<MakeLogicType<OverviewLogicValues, OverviewLogicActions>>({
|
||||
export const OverviewLogic = kea<MakeLogicType<OverviewLogicValues, {}>>({
|
||||
connect: {
|
||||
actions: [CachedFetchIndexApiLogic, ['apiError']],
|
||||
values: [CachedFetchIndexApiLogic, ['indexData', 'status']],
|
||||
},
|
||||
listeners: () => ({
|
||||
apiError: async (_, breakpoint) => {
|
||||
// show error for a second before navigating away
|
||||
await breakpoint(1000);
|
||||
KibanaLogic.values.navigateToUrl(CONNECTORS_PATH);
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'connector_detail', 'overview'],
|
||||
selectors: ({ selectors }) => ({
|
||||
isError: [() => [selectors.status], (status) => status === Status.ERROR],
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiButton, EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { EuiButton, EuiCallOut, EuiCode, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -18,20 +18,23 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../../../common/constants';
|
||||
|
||||
import { docLinks } from '../../../shared/doc_links';
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { isConnectorIndex } from '../../utils/indices';
|
||||
|
||||
import { EuiButtonTo } from '../../../shared/react_router_helpers/eui_components';
|
||||
import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes';
|
||||
import { ConvertConnectorLogic } from '../search_index/connector/native_connector_configuration/convert_connector_logic';
|
||||
import { IndexViewLogic } from '../search_index/index_view_logic';
|
||||
import { SyncJobs } from '../search_index/sync_jobs/sync_jobs';
|
||||
|
||||
import { ConvertConnectorModal } from '../shared/convert_connector_modal/convert_connector_modal';
|
||||
|
||||
import { ConnectorDetailTabId } from './connector_detail';
|
||||
import { ConnectorStats } from './connector_stats';
|
||||
import { ConnectorViewLogic } from './connector_view_logic';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
|
||||
export const ConnectorDetailOverview: React.FC = () => {
|
||||
const { indexData } = useValues(OverviewLogic);
|
||||
const { indexData } = useValues(IndexViewLogic);
|
||||
const { connector } = useValues(ConnectorViewLogic);
|
||||
const error = null;
|
||||
const { isCloud } = useValues(KibanaLogic);
|
||||
|
@ -40,8 +43,7 @@ export const ConnectorDetailOverview: React.FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
{isConnectorIndex(indexData) && error && (
|
||||
{error && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
iconType="warning"
|
||||
|
@ -59,7 +61,74 @@ export const ConnectorDetailOverview: React.FC = () => {
|
|||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
{isConnectorIndex(indexData) && indexData.connector.is_native && !isCloud && (
|
||||
{!!connector && !connector.index_name && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
iconType="iInCircle"
|
||||
color="danger"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectors.overview.connectorNoIndexCallOut.title',
|
||||
{
|
||||
defaultMessage: 'Connector has no attached index',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectors.overview.connectorNoIndexCallOut.description',
|
||||
{
|
||||
defaultMessage:
|
||||
"You won't be able to start syncing content until your connector is attached to an index.",
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiButtonTo
|
||||
color="danger"
|
||||
fill
|
||||
to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})}#attachIndexBox`}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectors.overview.connectorNoIndexCallOut.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'Attach index',
|
||||
}
|
||||
)}
|
||||
</EuiButtonTo>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
{!!connector?.index_name && !indexData && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
iconType="iInCircle"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.title',
|
||||
{
|
||||
defaultMessage: "Attached index doesn't exist",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.description"
|
||||
defaultMessage="The connector will create the index on its next sync, or you can manually create the index {indexName} with your desired settings and mappings."
|
||||
values={{
|
||||
indexName: <EuiCode>{connector.index_name}</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
{connector?.is_native && !isCloud && (
|
||||
<>
|
||||
{isModalVisible && <ConvertConnectorModal />}
|
||||
<EuiCallOut
|
||||
|
@ -103,10 +172,7 @@ export const ConnectorDetailOverview: React.FC = () => {
|
|||
</>
|
||||
)}
|
||||
{connector && connector.service_type !== ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE && (
|
||||
<ConnectorStats
|
||||
connector={connector}
|
||||
indexData={isConnectorIndex(indexData) ? indexData : undefined}
|
||||
/>
|
||||
<ConnectorStats connector={connector} indexData={indexData || undefined} />
|
||||
)}
|
||||
{connector && connector.service_type !== ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE && (
|
||||
<>
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
FetchConnectorsApiLogicActions,
|
||||
} from '../../api/connector/fetch_connectors.api';
|
||||
|
||||
export type ConnectorViewItem = Connector & { docsCount?: number };
|
||||
export type ConnectorViewItem = Connector & { docsCount?: number; indexExists: boolean };
|
||||
export interface ConnectorsActions {
|
||||
apiError: FetchConnectorsApiLogicActions['apiError'];
|
||||
apiSuccess: FetchConnectorsApiLogicActions['apiSuccess'];
|
||||
|
@ -192,6 +192,7 @@ export const ConnectorsLogic = kea<MakeLogicType<ConnectorsValues, ConnectorsAct
|
|||
return {
|
||||
...connector,
|
||||
docsCount: data?.counts[indexName],
|
||||
indexExists: data?.indexExists[indexName],
|
||||
};
|
||||
}
|
||||
return connector;
|
||||
|
|
|
@ -20,8 +20,6 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Connector, ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { Meta } from '../../../../../common/types/pagination';
|
||||
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
|
@ -41,7 +39,7 @@ interface ConnectorsTableProps {
|
|||
isLoading?: boolean;
|
||||
items: ConnectorViewItem[];
|
||||
meta?: Meta;
|
||||
onChange: (criteria: CriteriaWithPagination<Connector>) => void;
|
||||
onChange: (criteria: CriteriaWithPagination<ConnectorViewItem>) => void;
|
||||
onDelete: (connectorName: string, connectorId: string, indexName: string | null) => void;
|
||||
}
|
||||
export const ConnectorsTable: React.FC<ConnectorsTableProps> = ({
|
||||
|
@ -69,7 +67,7 @@ export const ConnectorsTable: React.FC<ConnectorsTableProps> = ({
|
|||
defaultMessage: 'Connector name',
|
||||
}
|
||||
),
|
||||
render: (connector: Connector) => (
|
||||
render: (connector: ConnectorViewItem) => (
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_PATH, { connectorId: connector.id })}
|
||||
>
|
||||
|
@ -81,18 +79,23 @@ export const ConnectorsTable: React.FC<ConnectorsTableProps> = ({
|
|||
]
|
||||
: []),
|
||||
{
|
||||
field: 'index_name',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectors.connectorTable.columns.indexName',
|
||||
{
|
||||
defaultMessage: 'Index name',
|
||||
}
|
||||
),
|
||||
render: (indexName: string) =>
|
||||
indexName ? (
|
||||
<EuiLinkTo to={generateEncodedPath(SEARCH_INDEX_PATH, { indexName })}>
|
||||
{indexName}
|
||||
</EuiLinkTo>
|
||||
render: (connector: ConnectorViewItem) =>
|
||||
connector.index_name ? (
|
||||
connector.indexExists ? (
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(SEARCH_INDEX_PATH, { indexName: connector.index_name })}
|
||||
>
|
||||
{connector.index_name}
|
||||
</EuiLinkTo>
|
||||
) : (
|
||||
connector.index_name
|
||||
)
|
||||
) : (
|
||||
'--'
|
||||
),
|
||||
|
@ -125,16 +128,19 @@ export const ConnectorsTable: React.FC<ConnectorsTableProps> = ({
|
|||
]
|
||||
: []),
|
||||
{
|
||||
field: 'status',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connectors.connectorTable.columns.status',
|
||||
{
|
||||
defaultMessage: 'Ingestion status',
|
||||
}
|
||||
),
|
||||
render: (connectorStatus: ConnectorStatus) => {
|
||||
const label = connectorStatusToText(connectorStatus);
|
||||
return <EuiBadge color={connectorStatusToColor(connectorStatus)}>{label}</EuiBadge>;
|
||||
render: (connector: ConnectorViewItem) => {
|
||||
const label = connectorStatusToText(connector.status, !!connector.index_name);
|
||||
return (
|
||||
<EuiBadge color={connectorStatusToColor(connector.status, !!connector.index_name)}>
|
||||
{label}
|
||||
</EuiBadge>
|
||||
);
|
||||
},
|
||||
truncateText: true,
|
||||
width: '15%',
|
||||
|
|
|
@ -40,14 +40,11 @@ import { EuiButtonTo, EuiLinkTo } from '../../../../shared/react_router_helpers'
|
|||
|
||||
import { GenerateConnectorApiKeyApiLogic } from '../../../api/connector/generate_connector_api_key_api_logic';
|
||||
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
|
||||
import { SEARCH_INDEX_TAB_PATH } from '../../../routes';
|
||||
import { isConnectorIndex } from '../../../utils/indices';
|
||||
import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes';
|
||||
|
||||
import { ConnectorDetailTabId } from '../../connector_detail/connector_detail';
|
||||
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
|
||||
import { SyncsContextMenu } from '../components/header_actions/syncs_context_menu';
|
||||
import { IndexNameLogic } from '../index_name_logic';
|
||||
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
import { SearchIndexTabId } from '../search_index';
|
||||
|
||||
import { ApiKeyConfig } from './api_key_configuration';
|
||||
import { ConnectorNameAndDescription } from './connector_name_and_description/connector_name_and_description';
|
||||
|
@ -56,34 +53,31 @@ import { NativeConnectorConfiguration } from './native_connector_configuration/n
|
|||
|
||||
export const ConnectorConfiguration: React.FC = () => {
|
||||
const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic);
|
||||
const { index, recheckIndexLoading } = useValues(IndexViewLogic);
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
const { recheckIndex } = useActions(IndexViewLogic);
|
||||
const { connector, fetchConnectorApiStatus } = useValues(ConnectorViewLogic);
|
||||
const { fetchConnector } = useActions(ConnectorViewLogic);
|
||||
const cloudContext = useCloudDetails();
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { status } = useValues(ConnectorConfigurationApiLogic);
|
||||
const { makeRequest } = useActions(ConnectorConfigurationApiLogic);
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
if (!isConnectorIndex(index)) {
|
||||
return <></>;
|
||||
if (!connector) {
|
||||
return <></>; // TODO: show nicer error message
|
||||
}
|
||||
|
||||
if (index.connector.is_native && index.connector.service_type) {
|
||||
if (connector.is_native && connector.service_type) {
|
||||
return <NativeConnectorConfiguration />;
|
||||
}
|
||||
|
||||
const hasApiKey = !!(index.connector.api_key_id ?? apiKeyData);
|
||||
const hasApiKey = !!(connector.api_key_id ?? apiKeyData);
|
||||
const docsUrl = CONNECTORS.find(
|
||||
({ serviceType }) => serviceType === index.connector.service_type
|
||||
({ serviceType }) => serviceType === connector.service_type
|
||||
)?.docsUrl;
|
||||
|
||||
// TODO service_type === "" is considered unknown/custom connector multipleplaces replace all of them with a better solution
|
||||
const isBeta =
|
||||
!index.connector.service_type ||
|
||||
Boolean(
|
||||
BETA_CONNECTORS.find(({ serviceType }) => serviceType === index.connector.service_type)
|
||||
);
|
||||
!connector.service_type ||
|
||||
Boolean(BETA_CONNECTORS.find(({ serviceType }) => serviceType === connector.service_type));
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -94,12 +88,19 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
<EuiSteps
|
||||
steps={[
|
||||
{
|
||||
children: (
|
||||
children: connector.index_name ? (
|
||||
<ApiKeyConfig
|
||||
indexName={indexName}
|
||||
hasApiKey={!!index.connector.api_key_id}
|
||||
indexName={connector.index_name}
|
||||
hasApiKey={!!connector.api_key_id}
|
||||
isNative={false}
|
||||
/>
|
||||
) : (
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.apiKey.noApiKeyLabel',
|
||||
{
|
||||
defaultMessage: 'Please set an index name before generating an API key',
|
||||
}
|
||||
)
|
||||
),
|
||||
status: hasApiKey ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
|
@ -112,7 +113,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
},
|
||||
{
|
||||
children: <ConnectorNameAndDescription />,
|
||||
status: index.connector.description ? 'complete' : 'incomplete',
|
||||
status: connector.description ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.nameAndDescriptionTitle',
|
||||
{
|
||||
|
@ -149,8 +150,8 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
{getConnectorTemplate({
|
||||
apiKeyData,
|
||||
connectorData: {
|
||||
id: index.connector.id,
|
||||
service_type: index.connector.service_type,
|
||||
id: connector.id,
|
||||
service_type: connector.service_type,
|
||||
},
|
||||
host: cloudContext.elasticsearchUrl,
|
||||
})}
|
||||
|
@ -168,7 +169,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
</>
|
||||
),
|
||||
status:
|
||||
!index.connector.status || index.connector.status === ConnectorStatus.CREATED
|
||||
!connector.status || connector.status === ConnectorStatus.CREATED
|
||||
? 'incomplete'
|
||||
: 'complete',
|
||||
title: i18n.translate(
|
||||
|
@ -182,14 +183,13 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
{
|
||||
children: (
|
||||
<ConnectorConfigurationComponent
|
||||
connector={index.connector}
|
||||
connector={connector}
|
||||
hasPlatinumLicense={hasPlatinumLicense}
|
||||
isLoading={status === Status.LOADING}
|
||||
saveConfig={(configuration) =>
|
||||
makeRequest({
|
||||
configuration,
|
||||
connectorId: index.connector.id,
|
||||
indexName: index.name,
|
||||
connectorId: connector.id, // TODO
|
||||
})
|
||||
}
|
||||
subscriptionLink={docLinks.licenseManagement}
|
||||
|
@ -197,8 +197,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
'/app/management/stack/license_management'
|
||||
)}
|
||||
>
|
||||
{!index.connector.status ||
|
||||
index.connector.status === ConnectorStatus.CREATED ? (
|
||||
{!connector.status || connector.status === ConnectorStatus.CREATED ? (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnectorTitle',
|
||||
|
@ -219,8 +218,8 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
<EuiButton
|
||||
data-telemetry-id="entSearchContent-connector-configuration-recheckNow"
|
||||
iconType="refresh"
|
||||
onClick={() => recheckIndex()}
|
||||
isLoading={recheckIndexLoading}
|
||||
onClick={() => fetchConnector({ connectorId: connector.id })}
|
||||
isLoading={fetchConnectorApiStatus === Status.LOADING}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnector.button.label',
|
||||
|
@ -239,7 +238,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
{
|
||||
defaultMessage:
|
||||
'Your connector {name} has connected to Search successfully.',
|
||||
values: { name: index.connector.name },
|
||||
values: { name: connector.name },
|
||||
}
|
||||
)}
|
||||
/>
|
||||
|
@ -247,9 +246,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
</ConnectorConfigurationComponent>
|
||||
),
|
||||
status:
|
||||
index.connector.status === ConnectorStatus.CONNECTED
|
||||
? 'complete'
|
||||
: 'incomplete',
|
||||
connector.status === ConnectorStatus.CONNECTED ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.enhance.title',
|
||||
{
|
||||
|
@ -278,9 +275,9 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
<EuiButtonTo
|
||||
data-test-subj="entSearchContent-connector-configuration-setScheduleAndSync"
|
||||
data-telemetry-id="entSearchContent-connector-configuration-setScheduleAndSync"
|
||||
to={`${generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName,
|
||||
tabId: SearchIndexTabId.SCHEDULING,
|
||||
to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.SCHEDULING,
|
||||
})}`}
|
||||
>
|
||||
{i18n.translate(
|
||||
|
@ -298,7 +295,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
status: index.connector.scheduling.full.enabled ? 'complete' : 'incomplete',
|
||||
status: connector.scheduling.full.enabled ? 'complete' : 'incomplete',
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.title',
|
||||
{
|
||||
|
|
|
@ -30,12 +30,9 @@ import { KibanaLogic } from '../../../../shared/kibana';
|
|||
import { LicensingLogic } from '../../../../shared/licensing';
|
||||
import { EuiButtonTo, EuiLinkTo } from '../../../../shared/react_router_helpers';
|
||||
import { UnsavedChangesPrompt } from '../../../../shared/unsaved_changes_prompt';
|
||||
import { SEARCH_INDEX_TAB_PATH } from '../../../routes';
|
||||
import { IngestionStatus } from '../../../types';
|
||||
import * as indices from '../../../utils/indices';
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
|
||||
import { SearchIndexTabId } from '../search_index';
|
||||
import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes';
|
||||
import { ConnectorDetailTabId } from '../../connector_detail/connector_detail';
|
||||
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
|
||||
|
||||
import { ConnectorContentScheduling } from './connector_scheduling/full_content';
|
||||
import { ConnectorSchedulingLogic } from './connector_scheduling_logic';
|
||||
|
@ -67,9 +64,8 @@ export const SchedulePanel: React.FC<SchedulePanelProps> = ({ title, description
|
|||
|
||||
export const ConnectorSchedulingComponent: React.FC = () => {
|
||||
const { productFeatures } = useValues(KibanaLogic);
|
||||
const { ingestionStatus, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature } =
|
||||
useValues(IndexViewLogic);
|
||||
const { index } = useValues(IndexViewLogic);
|
||||
const { connector, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature } =
|
||||
useValues(ConnectorViewLogic);
|
||||
const { hasChanges } = useValues(ConnectorSchedulingLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
|
||||
|
@ -77,16 +73,16 @@ export const ConnectorSchedulingComponent: React.FC = () => {
|
|||
hasIncrementalSyncFeature && productFeatures.hasIncrementalSyncEnabled;
|
||||
const shouldShowAccessControlSync =
|
||||
hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled;
|
||||
if (!indices.isConnectorIndex(index)) {
|
||||
if (!connector) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const isDocumentLevelSecurityDisabled =
|
||||
!index.connector.configuration.use_document_level_security?.value;
|
||||
!connector.configuration.use_document_level_security?.value;
|
||||
|
||||
if (
|
||||
index.connector.status === ConnectorStatus.CREATED ||
|
||||
index.connector.status === ConnectorStatus.NEEDS_CONFIGURATION
|
||||
connector.status === ConnectorStatus.CREATED ||
|
||||
connector.status === ConnectorStatus.NEEDS_CONFIGURATION
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
|
@ -112,9 +108,9 @@ export const ConnectorSchedulingComponent: React.FC = () => {
|
|||
<EuiSpacer size="s" />
|
||||
<EuiButtonTo
|
||||
data-telemetry-id="entSearchContent-connector-scheduling-configure"
|
||||
to={generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName: index.name,
|
||||
tabId: SearchIndexTabId.CONFIGURATION,
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})}
|
||||
fill
|
||||
size="s"
|
||||
|
@ -141,7 +137,7 @@ export const ConnectorSchedulingComponent: React.FC = () => {
|
|||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
{ingestionStatus === IngestionStatus.ERROR ? (
|
||||
{connector.status === ConnectorStatus.ERROR ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
|
@ -191,11 +187,14 @@ export const ConnectorSchedulingComponent: React.FC = () => {
|
|||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<ConnectorContentScheduling type={SyncJobType.FULL} index={index} />
|
||||
<ConnectorContentScheduling type={SyncJobType.FULL} connector={connector} />
|
||||
</EuiFlexItem>
|
||||
{shouldShowIncrementalSync && (
|
||||
<EuiFlexItem>
|
||||
<ConnectorContentScheduling type={SyncJobType.INCREMENTAL} index={index} />
|
||||
<ConnectorContentScheduling
|
||||
type={SyncJobType.INCREMENTAL}
|
||||
connector={connector}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
@ -220,7 +219,7 @@ export const ConnectorSchedulingComponent: React.FC = () => {
|
|||
>
|
||||
<ConnectorContentScheduling
|
||||
type={SyncJobType.ACCESS_CONTROL}
|
||||
index={index}
|
||||
connector={connector}
|
||||
hasPlatinumLicense={hasPlatinumLicense}
|
||||
/>
|
||||
</SchedulePanel>
|
||||
|
@ -242,9 +241,9 @@ export const ConnectorSchedulingComponent: React.FC = () => {
|
|||
values={{
|
||||
link: (
|
||||
<EuiLinkTo
|
||||
to={generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName: index.name,
|
||||
tabId: SearchIndexTabId.CONFIGURATION,
|
||||
to={generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})}
|
||||
>
|
||||
{i18n.translate(
|
||||
|
|
|
@ -23,9 +23,7 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SyncJobType } from '@kbn/search-connectors';
|
||||
|
||||
import { ConnectorViewIndex, CrawlerViewIndex } from '../../../../types';
|
||||
import { Connector, SyncJobType } from '@kbn/search-connectors';
|
||||
|
||||
import { PlatinumLicensePopover } from '../../../shared/platinum_license_popover/platinum_license_popover';
|
||||
import { ConnectorSchedulingLogic } from '../connector_scheduling_logic';
|
||||
|
@ -34,7 +32,7 @@ import { ConnectorCronEditor } from './connector_cron_editor';
|
|||
|
||||
export interface ConnectorContentSchedulingProps {
|
||||
hasPlatinumLicense?: boolean;
|
||||
index: CrawlerViewIndex | ConnectorViewIndex;
|
||||
connector: Connector;
|
||||
type: SyncJobType;
|
||||
}
|
||||
const getAccordionTitle = (type: ConnectorContentSchedulingProps['type']) => {
|
||||
|
@ -103,11 +101,11 @@ const EnableSwitch: React.FC<{
|
|||
|
||||
export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProps> = ({
|
||||
type,
|
||||
index,
|
||||
connector,
|
||||
hasPlatinumLicense = false,
|
||||
}) => {
|
||||
const { setHasChanges, updateScheduling } = useActions(ConnectorSchedulingLogic);
|
||||
const schedulingInput = index.connector.scheduling;
|
||||
const schedulingInput = connector.scheduling;
|
||||
const [scheduling, setScheduling] = useState(schedulingInput);
|
||||
const [isAccordionOpen, setIsAccordionOpen] = useState<'open' | 'closed'>(
|
||||
scheduling[type].enabled ? 'open' : 'closed'
|
||||
|
@ -116,7 +114,7 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp
|
|||
|
||||
const isGated = !hasPlatinumLicense && type === SyncJobType.ACCESS_CONTROL;
|
||||
const isDocumentLevelSecurityDisabled =
|
||||
!index.connector.configuration.use_document_level_security?.value;
|
||||
!connector.configuration.use_document_level_security?.value;
|
||||
|
||||
const isEnableSwitchDisabled =
|
||||
type === SyncJobType.ACCESS_CONTROL && (!hasPlatinumLicense || isDocumentLevelSecurityDisabled);
|
||||
|
@ -231,9 +229,9 @@ export const ConnectorContentScheduling: React.FC<ConnectorContentSchedulingProp
|
|||
}}
|
||||
onSave={(interval) => {
|
||||
updateScheduling(type, {
|
||||
connectorId: index.connector.id,
|
||||
connectorId: connector.id,
|
||||
scheduling: {
|
||||
...index.connector.scheduling,
|
||||
...connector.scheduling,
|
||||
[type]: {
|
||||
...scheduling[type],
|
||||
interval,
|
||||
|
|
|
@ -20,7 +20,8 @@ import {
|
|||
ConvertConnectorApiLogicArgs,
|
||||
ConvertConnectorApiLogicResponse,
|
||||
} from '../../../../api/connector/convert_connector_api_logic';
|
||||
import { IndexViewActions, IndexViewLogic } from '../../index_view_logic';
|
||||
import { ConnectorViewLogic } from '../../../connector_detail/connector_view_logic';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
interface ConvertConnectorValues {
|
||||
connectorId: typeof IndexViewLogic.values.connectorId;
|
||||
|
@ -34,7 +35,6 @@ type ConvertConnectorActions = Pick<
|
|||
'apiError' | 'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
convertConnector(): void;
|
||||
fetchIndex(): IndexViewActions['fetchIndex'];
|
||||
hideModal(): void;
|
||||
showModal(): void;
|
||||
};
|
||||
|
@ -49,15 +49,13 @@ export const ConvertConnectorLogic = kea<
|
|||
showModal: () => true,
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
ConvertConnectorApiLogic,
|
||||
['apiError', 'apiSuccess', 'makeRequest'],
|
||||
IndexViewLogic,
|
||||
['fetchIndex'],
|
||||
],
|
||||
values: [ConvertConnectorApiLogic, ['status'], IndexViewLogic, ['connectorId']],
|
||||
actions: [ConvertConnectorApiLogic, ['apiError', 'apiSuccess', 'makeRequest']],
|
||||
values: [ConvertConnectorApiLogic, ['status'], ConnectorViewLogic, ['connectorId']],
|
||||
},
|
||||
listeners: ({ actions, values }) => ({
|
||||
apiSuccess: () => {
|
||||
actions.hideModal();
|
||||
},
|
||||
convertConnector: () => {
|
||||
if (values.connectorId) {
|
||||
actions.makeRequest({ connectorId: values.connectorId });
|
||||
|
|
|
@ -24,7 +24,6 @@ import { HttpLogic } from '../../../../../shared/http';
|
|||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
|
||||
import { ConnectorConfigurationApiLogic } from '../../../../api/connector/update_connector_configuration_api_logic';
|
||||
import { IndexNameLogic } from '../../index_name_logic';
|
||||
import { ConnectorDefinition } from '../types';
|
||||
|
||||
interface NativeConnectorConfigurationConfigProps {
|
||||
|
@ -37,7 +36,6 @@ export const NativeConnectorConfigurationConfig: React.FC<
|
|||
NativeConnectorConfigurationConfigProps
|
||||
> = ({ connector, nativeConnector, status }) => {
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
const { status: updateStatus } = useValues(ConnectorConfigurationApiLogic);
|
||||
const { makeRequest } = useActions(ConnectorConfigurationApiLogic);
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
@ -50,7 +48,6 @@ export const NativeConnectorConfigurationConfig: React.FC<
|
|||
makeRequest({
|
||||
configuration,
|
||||
connectorId: connector.id,
|
||||
indexName,
|
||||
})
|
||||
}
|
||||
subscriptionLink={docLinks.licenseManagement}
|
||||
|
|
|
@ -8,7 +8,15 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
export function connectorStatusToText(connectorStatus: ConnectorStatus): string {
|
||||
const incompleteText = i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.incomplete.label',
|
||||
{ defaultMessage: 'Incomplete' }
|
||||
);
|
||||
|
||||
export function connectorStatusToText(
|
||||
connectorStatus: ConnectorStatus,
|
||||
hasIndexName: boolean
|
||||
): string {
|
||||
if (
|
||||
connectorStatus === ConnectorStatus.CREATED ||
|
||||
connectorStatus === ConnectorStatus.NEEDS_CONFIGURATION
|
||||
|
@ -24,6 +32,9 @@ export function connectorStatusToText(connectorStatus: ConnectorStatus): string
|
|||
{ defaultMessage: 'Connector Failure' }
|
||||
);
|
||||
}
|
||||
if (!hasIndexName) {
|
||||
return incompleteText;
|
||||
}
|
||||
if (connectorStatus === ConnectorStatus.CONFIGURED) {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.configured.label',
|
||||
|
@ -37,15 +48,16 @@ export function connectorStatusToText(connectorStatus: ConnectorStatus): string
|
|||
);
|
||||
}
|
||||
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.incomplete.label',
|
||||
{ defaultMessage: 'Incomplete' }
|
||||
);
|
||||
return incompleteText;
|
||||
}
|
||||
|
||||
export function connectorStatusToColor(
|
||||
connectorStatus: ConnectorStatus
|
||||
connectorStatus: ConnectorStatus,
|
||||
hasIndexName: boolean
|
||||
): 'warning' | 'danger' | 'success' {
|
||||
if (!hasIndexName) {
|
||||
return 'warning';
|
||||
}
|
||||
if (connectorStatus === ConnectorStatus.CONNECTED) {
|
||||
return 'success';
|
||||
}
|
||||
|
|
|
@ -10,8 +10,13 @@ import { IScopedClusterClient } from '@kbn/core/server';
|
|||
export const fetchIndexCounts = async (client: IScopedClusterClient, indicesNames: string[]) => {
|
||||
// TODO: is there way to batch this? Passing multiple index names or a pattern still returns a singular count
|
||||
const countPromises = indicesNames.map(async (indexName) => {
|
||||
const { count } = await client.asCurrentUser.count({ index: indexName });
|
||||
return { [indexName]: count };
|
||||
try {
|
||||
const { count } = await client.asCurrentUser.count({ index: indexName });
|
||||
return { [indexName]: count };
|
||||
} catch {
|
||||
// we don't want to error out the whole API call if one index breaks (eg: doesn't exist or is closed)
|
||||
return { [indexName]: 0 };
|
||||
}
|
||||
});
|
||||
const indexCountArray = await Promise.all(countPromises);
|
||||
return indexCountArray.reduce((acc, current) => Object.assign(acc, current), {});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
CONNECTORS_INDEX,
|
||||
|
@ -501,6 +502,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
|
||||
let connectorResult;
|
||||
let connectorCountResult;
|
||||
let connectorResultSlice;
|
||||
const indicesExist: Record<string, boolean> = {};
|
||||
try {
|
||||
connectorResult = await fetchConnectors(
|
||||
client.asCurrentUser,
|
||||
|
@ -509,14 +512,21 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
searchQuery
|
||||
);
|
||||
|
||||
const indicesSlice = connectorResult
|
||||
.reduce((acc: string[], connector) => {
|
||||
if (connector.index_name) {
|
||||
acc.push(connector.index_name);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
.slice(from, from + size);
|
||||
connectorResultSlice = connectorResult.slice(from, from + size);
|
||||
for (const connector of connectorResultSlice) {
|
||||
if (connector.index_name) {
|
||||
const indexExists = await client.asCurrentUser.indices.exists({
|
||||
index: connector.index_name,
|
||||
});
|
||||
indicesExist[connector.index_name] = indexExists;
|
||||
}
|
||||
}
|
||||
const indicesSlice = connectorResultSlice.reduce((acc: string[], connector) => {
|
||||
if (connector.index_name) {
|
||||
acc.push(connector.index_name);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
connectorCountResult = await fetchIndexCounts(client, indicesSlice);
|
||||
} catch (error) {
|
||||
if (isExpensiveQueriesNotAllowedException(error)) {
|
||||
|
@ -537,8 +547,9 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
}
|
||||
return response.ok({
|
||||
body: {
|
||||
connectors: connectorResult.slice(from, from + size),
|
||||
connectors: connectorResultSlice,
|
||||
counts: connectorCountResult,
|
||||
indexExists: indicesExist,
|
||||
meta: {
|
||||
page: {
|
||||
from,
|
||||
|
@ -562,13 +573,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const { connectorId } = request.params;
|
||||
const connectorResult = await fetchConnectorById(client.asCurrentUser, connectorId);
|
||||
|
||||
let connectorResult;
|
||||
try {
|
||||
connectorResult = await fetchConnectorById(client.asCurrentUser, connectorId);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
return response.ok({
|
||||
body: {
|
||||
connector: connectorResult,
|
||||
|
@ -605,7 +611,12 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
if (indexNameToDelete) {
|
||||
await deleteIndexPipelines(client, indexNameToDelete);
|
||||
await deleteAccessControlIndex(client, indexNameToDelete);
|
||||
await client.asCurrentUser.indices.delete({ index: indexNameToDelete });
|
||||
const indexExists = await client.asCurrentUser.indices.exists({
|
||||
index: indexNameToDelete,
|
||||
});
|
||||
if (indexExists) {
|
||||
await client.asCurrentUser.indices.delete({ index: indexNameToDelete });
|
||||
}
|
||||
}
|
||||
if (apiKeyId) {
|
||||
await client.asCurrentUser.security.invalidateApiKey({ ids: [apiKeyId] });
|
||||
|
@ -677,12 +688,7 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
return response.ok();
|
||||
} catch (error) {
|
||||
if (isIndexNotFoundException(error)) {
|
||||
return createError({
|
||||
errorCode: ErrorCode.INDEX_NOT_FOUND,
|
||||
message: `Could not find index ${indexName}`,
|
||||
response,
|
||||
statusCode: 404,
|
||||
});
|
||||
return response.ok();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,11 @@ import { DEFAULT_PIPELINE_NAME } from '../../../common/constants';
|
|||
import { ErrorCode } from '../../../common/types/error_codes';
|
||||
import { AlwaysShowPattern } from '../../../common/types/indices';
|
||||
|
||||
import type { AttachMlInferencePipelineResponse } from '../../../common/types/pipelines';
|
||||
import type {
|
||||
AttachMlInferencePipelineResponse,
|
||||
MlInferenceError,
|
||||
MlInferenceHistoryResponse,
|
||||
} from '../../../common/types/pipelines';
|
||||
|
||||
import { fetchCrawlerByIndexName, fetchCrawlers } from '../../lib/crawler/fetch_crawlers';
|
||||
|
||||
|
@ -774,8 +778,14 @@ export function registerIndexRoutes({
|
|||
const indexName = decodeURIComponent(request.params.indexName);
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
|
||||
const errors = await getMlInferenceErrors(indexName, client.asCurrentUser);
|
||||
|
||||
let errors: MlInferenceError[] = [];
|
||||
try {
|
||||
errors = await getMlInferenceErrors(indexName, client.asCurrentUser);
|
||||
} catch (error) {
|
||||
if (!isIndexNotFoundException(error)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return response.ok({
|
||||
body: {
|
||||
errors,
|
||||
|
@ -914,9 +924,14 @@ export function registerIndexRoutes({
|
|||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const indexName = decodeURIComponent(request.params.indexName);
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
|
||||
const history = await fetchMlInferencePipelineHistory(client.asCurrentUser, indexName);
|
||||
|
||||
let history: MlInferenceHistoryResponse = { history: [] };
|
||||
try {
|
||||
history = await fetchMlInferencePipelineHistory(client.asCurrentUser, indexName);
|
||||
} catch (error) {
|
||||
if (!isIndexNotFoundException(error)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return response.ok({
|
||||
body: history,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue