[8.13] [Search] Fix connectors pages breaking if index has been deleted (#178147) (#178224)

# 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:
Sander Philipse 2024-03-07 17:42:37 +01:00 committed by GitHub
parent 017008011d
commit 57485255f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 404 additions and 277 deletions

View file

@ -21,6 +21,7 @@ export interface FetchConnectorsApiLogicArgs {
export interface FetchConnectorsApiLogicResponse {
connectors: Connector[];
counts: Record<string, number>;
indexExists: Record<string, boolean>;
isInitialRequest: boolean;
meta: Meta;
}

View file

@ -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' });
});
});
});

View file

@ -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(

View file

@ -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', {

View file

@ -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}

View file

@ -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(

View file

@ -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>

View file

@ -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>
}

View file

@ -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],

View file

@ -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 && (
<>

View file

@ -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;

View file

@ -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%',

View file

@ -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',
{

View file

@ -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(

View file

@ -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,

View file

@ -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 });

View file

@ -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}

View file

@ -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';
}

View file

@ -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), {});

View file

@ -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;
}

View file

@ -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' },