mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Enterprise Search] Add UI to manage index and default pipelines (#140767)
This commit is contained in:
parent
22e78f9bb9
commit
d0a0a8f889
36 changed files with 1943 additions and 53 deletions
|
@ -7,6 +7,8 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IngestPipelineParams } from './types/connectors';
|
||||
|
||||
export const ENTERPRISE_SEARCH_OVERVIEW_PLUGIN = {
|
||||
ID: 'enterpriseSearch',
|
||||
NAME: i18n.translate('xpack.enterpriseSearch.overview.productName', {
|
||||
|
@ -118,3 +120,11 @@ export const WORKPLACE_SEARCH_URL = '/app/enterprise_search/workplace_search';
|
|||
export const ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT = 25;
|
||||
|
||||
export const ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE = 'elastic-crawler';
|
||||
|
||||
export const DEFAULT_PIPELINE_NAME = 'ent-search-generic-ingestion';
|
||||
export const DEFAULT_PIPELINE_VALUES: IngestPipelineParams = {
|
||||
extract_binary_content: true,
|
||||
name: DEFAULT_PIPELINE_NAME,
|
||||
reduce_whitespace: true,
|
||||
run_ml_inference: false,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockHttpValues } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { getDefaultPipeline } from './get_default_pipeline_api_logic';
|
||||
|
||||
describe('getDefaultPipelineApiLogic', () => {
|
||||
const { http } = mockHttpValues;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('updatePipeline', () => {
|
||||
it('calls correct api', async () => {
|
||||
const promise = Promise.resolve('result');
|
||||
http.get.mockReturnValue(promise);
|
||||
const result = getDefaultPipeline();
|
||||
await nextTick();
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/enterprise_search/connectors/default_pipeline'
|
||||
);
|
||||
await expect(result).resolves.toEqual('result');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../common/types/connectors';
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export type FetchDefaultPipelineResponse = IngestPipelineParams;
|
||||
|
||||
export const getDefaultPipeline = async (): Promise<FetchDefaultPipelineResponse> => {
|
||||
const route = '/internal/enterprise_search/connectors/default_pipeline';
|
||||
|
||||
return await HttpLogic.values.http.get(route);
|
||||
};
|
||||
|
||||
export const FetchDefaultPipelineApiLogic = createApiLogic(
|
||||
['content', 'get_default_pipeline_api_logic'],
|
||||
getDefaultPipeline
|
||||
);
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockHttpValues } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { updateDefaultPipeline } from './update_default_pipeline_api_logic';
|
||||
|
||||
describe('updateDefaultPipelineApiLogic', () => {
|
||||
const { http } = mockHttpValues;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('updateDefaultPipeline', () => {
|
||||
it('calls correct api', async () => {
|
||||
const promise = Promise.resolve('result');
|
||||
http.post.mockReturnValue(promise);
|
||||
const pipeline = {
|
||||
extract_binary_content: true,
|
||||
name: 'pipelineName',
|
||||
reduce_whitespace: false,
|
||||
run_ml_inference: true,
|
||||
};
|
||||
const result = updateDefaultPipeline(pipeline);
|
||||
await nextTick();
|
||||
expect(http.put).toHaveBeenCalledWith(
|
||||
'/internal/enterprise_search/connectors/default_pipeline',
|
||||
{
|
||||
body: JSON.stringify(pipeline),
|
||||
}
|
||||
);
|
||||
await expect(result).resolves.toEqual(pipeline);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../common/types/connectors';
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export type PostDefaultPipelineResponse = IngestPipelineParams;
|
||||
export type PostDefaultPipelineArgs = IngestPipelineParams;
|
||||
|
||||
export const updateDefaultPipeline = async (
|
||||
pipeline: IngestPipelineParams
|
||||
): Promise<PostDefaultPipelineResponse> => {
|
||||
const route = '/internal/enterprise_search/connectors/default_pipeline';
|
||||
|
||||
await HttpLogic.values.http.put(route, { body: JSON.stringify(pipeline) });
|
||||
|
||||
return pipeline;
|
||||
};
|
||||
|
||||
export const UpdateDefaultPipelineApiLogic = createApiLogic(
|
||||
['content', 'update_default_pipeline_api_logic'],
|
||||
updateDefaultPipeline
|
||||
);
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockHttpValues } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { updatePipeline } from './update_pipeline_api_logic';
|
||||
|
||||
describe('updatePipelineApiLogic', () => {
|
||||
const { http } = mockHttpValues;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('updatePipeline', () => {
|
||||
it('calls correct api', async () => {
|
||||
const promise = Promise.resolve('result');
|
||||
http.post.mockReturnValue(promise);
|
||||
const pipeline = {
|
||||
extract_binary_content: true,
|
||||
name: 'pipelineName',
|
||||
reduce_whitespace: false,
|
||||
run_ml_inference: true,
|
||||
};
|
||||
const result = updatePipeline({ connectorId: 'connector_id', pipeline });
|
||||
await nextTick();
|
||||
expect(http.put).toHaveBeenCalledWith(
|
||||
'/internal/enterprise_search/connectors/connector_id/pipeline',
|
||||
{
|
||||
body: JSON.stringify(pipeline),
|
||||
}
|
||||
);
|
||||
await expect(result).resolves.toEqual({ connectorId: 'connector_id', pipeline });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../common/types/connectors';
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export interface PostPipelineArgs {
|
||||
connectorId: string;
|
||||
pipeline: IngestPipelineParams;
|
||||
}
|
||||
|
||||
export interface PostPipelineResponse {
|
||||
connectorId: string;
|
||||
pipeline: IngestPipelineParams;
|
||||
}
|
||||
|
||||
export const updatePipeline = async ({
|
||||
connectorId,
|
||||
pipeline,
|
||||
}: PostPipelineArgs): Promise<PostPipelineResponse> => {
|
||||
const route = `/internal/enterprise_search/connectors/${connectorId}/pipeline`;
|
||||
|
||||
await HttpLogic.values.http.put(route, {
|
||||
body: JSON.stringify(pipeline),
|
||||
});
|
||||
return { connectorId, pipeline };
|
||||
};
|
||||
|
||||
export const UpdatePipelineApiLogic = createApiLogic(
|
||||
['content', 'update_pipeline_api_logic'],
|
||||
updatePipeline
|
||||
);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export interface CreateCustomPipelineApiLogicArgs {
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export interface CreateCustomPipelineApiLogicResponse {
|
||||
created: string[];
|
||||
}
|
||||
|
||||
export const createCustomPipeline = async ({
|
||||
indexName,
|
||||
}: CreateCustomPipelineApiLogicArgs): Promise<CreateCustomPipelineApiLogicResponse> => {
|
||||
const route = `/internal/enterprise_search/indices/${indexName}/pipelines`;
|
||||
const result = await HttpLogic.values.http.post<CreateCustomPipelineApiLogicResponse>(route);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const CreateCustomPipelineApiLogic = createApiLogic(
|
||||
['content', 'create_custom_pipeline_api_logic'],
|
||||
createCustomPipeline
|
||||
);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mockHttpValues } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { fetchCustomPipeline } from './fetch_custom_pipeline_api_logic';
|
||||
|
||||
describe('updatePipelineApiLogic', () => {
|
||||
const { http } = mockHttpValues;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('updatePipeline', () => {
|
||||
it('calls correct api', async () => {
|
||||
const promise = Promise.resolve('result');
|
||||
http.get.mockReturnValue(promise);
|
||||
const result = fetchCustomPipeline({ indexName: 'index_20' });
|
||||
await nextTick();
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/internal/enterprise_search/indices/index_20/pipelines'
|
||||
);
|
||||
await expect(result).resolves.toEqual('result');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export interface FetchCustomPipelineApiLogicArgs {
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export type FetchCustomPipelineApiLogicResponse = Record<string, IngestPipeline | undefined>;
|
||||
|
||||
export const fetchCustomPipeline = async ({
|
||||
indexName,
|
||||
}: FetchCustomPipelineApiLogicArgs): Promise<FetchCustomPipelineApiLogicResponse> => {
|
||||
const route = `/internal/enterprise_search/indices/${indexName}/pipelines`;
|
||||
const result = await HttpLogic.values.http.get<FetchCustomPipelineApiLogicResponse>(route);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const FetchCustomPipelineApiLogic = createApiLogic(
|
||||
['content', 'fetch_custom_pipeline_api_logic'],
|
||||
fetchCustomPipeline
|
||||
);
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiCodeBlock } from '@elastic/eui';
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../../../common/types/connectors';
|
||||
import { useCloudDetails } from '../../../../../shared/cloud_details/cloud_details';
|
||||
|
||||
import { decodeCloudId } from '../../../../utils/decode_cloud_id';
|
||||
|
||||
interface CurlRequestParams {
|
||||
apiKey?: string;
|
||||
document?: Record<string, unknown>;
|
||||
indexName: string;
|
||||
pipeline?: IngestPipelineParams;
|
||||
}
|
||||
|
||||
export const CurlRequest: React.FC<CurlRequestParams> = ({
|
||||
indexName,
|
||||
apiKey,
|
||||
document,
|
||||
pipeline,
|
||||
}) => {
|
||||
const cloudContext = useCloudDetails();
|
||||
|
||||
const DEFAULT_URL = 'https://localhost:9200';
|
||||
const baseUrl =
|
||||
(cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL;
|
||||
const apiKeyExample = apiKey || '<Replace_with_created_API_key>';
|
||||
const { name: pipelineName, ...pipelineParams } = pipeline ?? {};
|
||||
// We have to prefix the parameters with an underscore because that's what the actual pipeline looks for
|
||||
const pipelineArgs = Object.entries(pipelineParams).reduce(
|
||||
(acc: Record<string, boolean | undefined>, curr) => ({ ...acc, [`_${curr[0]}`]: curr[1] }),
|
||||
{}
|
||||
);
|
||||
|
||||
const inputDocument = pipeline ? { ...document, ...pipelineArgs } : document;
|
||||
|
||||
return (
|
||||
<EuiCodeBlock language="bash" fontSize="m" isCopyable>
|
||||
{`\
|
||||
curl -X POST '${baseUrl}/${indexName}/_doc${pipeline ? `?pipeline=${pipelineName}` : ''}' \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-H 'Authorization: ApiKey ${apiKeyExample}' \\
|
||||
-d '${JSON.stringify(inputDocument, null, 2)}'
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
};
|
|
@ -5,48 +5,38 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiCodeBlock,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiSwitch, EuiTitle } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useCloudDetails } from '../../../shared/cloud_details/cloud_details';
|
||||
import { decodeCloudId } from '../../utils/decode_cloud_id';
|
||||
|
||||
import { DOCUMENTS_API_JSON_EXAMPLE } from '../new_index/constants';
|
||||
|
||||
import { SettingsLogic } from '../settings/settings_logic';
|
||||
|
||||
import { ClientLibrariesPopover } from './components/client_libraries_popover/popover';
|
||||
import { CurlRequest } from './components/curl_request/curl_request';
|
||||
import { GenerateApiKeyModal } from './components/generate_api_key_modal/modal';
|
||||
import { ManageKeysPopover } from './components/manage_api_keys_popover/popover';
|
||||
|
||||
import { IndexViewLogic } from './index_view_logic';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
|
||||
export const GenerateApiKeyPanel: React.FC = () => {
|
||||
const { apiKey, isGenerateModalOpen, indexData } = useValues(OverviewLogic);
|
||||
const { apiKey, isGenerateModalOpen } = useValues(OverviewLogic);
|
||||
const { indexName } = useValues(IndexViewLogic);
|
||||
const { closeGenerateModal } = useActions(OverviewLogic);
|
||||
const { defaultPipeline } = useValues(SettingsLogic);
|
||||
|
||||
const cloudContext = useCloudDetails();
|
||||
|
||||
const DEFAULT_URL = 'https://localhost:9200';
|
||||
const searchIndexApiUrl =
|
||||
(cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL;
|
||||
|
||||
const apiKeyExample = apiKey || '<Create an API Key>';
|
||||
const [optimizedRequest, setOptimizedRequest] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isGenerateModalOpen && (
|
||||
<GenerateApiKeyModal indexName={indexData?.name ?? ''} onClose={closeGenerateModal} />
|
||||
<GenerateApiKeyModal indexName={indexName} onClose={closeGenerateModal} />
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
|
@ -55,7 +45,7 @@ export const GenerateApiKeyPanel: React.FC = () => {
|
|||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
{indexData?.name[0] !== '.' && (
|
||||
{indexName[0] !== '.' && (
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
|
@ -66,6 +56,16 @@ export const GenerateApiKeyPanel: React.FC = () => {
|
|||
</EuiTitle>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
onChange={(event) => setOptimizedRequest(event.target.checked)}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.overview.optimizedRequest.label',
|
||||
{ defaultMessage: 'View Enterprise Search optimized request' }
|
||||
)}
|
||||
checked={optimizedRequest}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
|
@ -78,18 +78,16 @@ export const GenerateApiKeyPanel: React.FC = () => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{indexData?.name[0] !== '.' && (
|
||||
{indexName[0] !== '.' && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem>
|
||||
<EuiCodeBlock language="bash" fontSize="m" isCopyable>
|
||||
{`\
|
||||
curl -X POST '${searchIndexApiUrl}/${indexData?.name}/_doc' \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-H 'Authorization: ApiKey ${apiKeyExample}' \\
|
||||
-d '${JSON.stringify(DOCUMENTS_API_JSON_EXAMPLE, null, 2)}'
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
<CurlRequest
|
||||
apiKey={apiKey}
|
||||
document={DOCUMENTS_API_JSON_EXAMPLE}
|
||||
indexName={indexName}
|
||||
pipeline={optimizedRequest ? defaultPipeline : undefined}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -9,7 +9,11 @@ import { kea, MakeLogicType } from 'kea';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SyncStatus } from '../../../../../common/types/connectors';
|
||||
import {
|
||||
Connector,
|
||||
IngestPipelineParams,
|
||||
SyncStatus,
|
||||
} from '../../../../../common/types/connectors';
|
||||
import { Actions } from '../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
flashAPIErrors,
|
||||
|
@ -62,6 +66,7 @@ export interface IndexViewActions {
|
|||
}
|
||||
|
||||
export interface IndexViewValues {
|
||||
connector: Connector | undefined;
|
||||
connectorId: string | null;
|
||||
data: typeof FetchIndexApiLogic.values.data;
|
||||
fetchIndexTimeoutId: NodeJS.Timeout | null;
|
||||
|
@ -73,6 +78,7 @@ export interface IndexViewValues {
|
|||
isWaitingForSync: boolean;
|
||||
lastUpdated: string | null;
|
||||
localSyncNowValue: boolean; // holds local value after update so UI updates correctly
|
||||
pipelineData: IngestPipelineParams | undefined;
|
||||
recheckIndexLoading: boolean;
|
||||
resetFetchIndexLoading: boolean;
|
||||
syncStatus: SyncStatus | null;
|
||||
|
@ -217,6 +223,13 @@ export const IndexViewLogic = kea<MakeLogicType<IndexViewValues, IndexViewAction
|
|||
],
|
||||
},
|
||||
selectors: ({ selectors }) => ({
|
||||
connector: [
|
||||
() => [selectors.index],
|
||||
(index: ElasticsearchViewIndex | undefined) =>
|
||||
index && (isConnectorViewIndex(index) || isCrawlerIndex(index))
|
||||
? index.connector
|
||||
: undefined,
|
||||
],
|
||||
connectorId: [
|
||||
() => [selectors.index],
|
||||
(index) => (isConnectorViewIndex(index) ? index.connector.id : null),
|
||||
|
@ -233,6 +246,10 @@ export const IndexViewLogic = kea<MakeLogicType<IndexViewValues, IndexViewAction
|
|||
(data, localSyncNowValue) => data?.connector?.sync_now || localSyncNowValue,
|
||||
],
|
||||
lastUpdated: [() => [selectors.data], (data) => getLastUpdated(data)],
|
||||
pipelineData: [
|
||||
() => [selectors.connector],
|
||||
(connector: Connector | undefined) => connector?.pipeline ?? undefined,
|
||||
],
|
||||
syncStatus: [() => [selectors.data], (data) => data?.connector?.last_sync_status ?? null],
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText, EuiBadge } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers';
|
||||
|
||||
export const CustomPipelinePanel: React.FC<{
|
||||
indexName: string;
|
||||
pipelineSuffix: string;
|
||||
processorsCount: number;
|
||||
}> = ({ indexName, pipelineSuffix, processorsCount }) => {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h4>{`${indexName}@${pipelineSuffix}`}</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmptyTo
|
||||
to={`/app/management/ingest/ingest_pipelines/?pipeline=${indexName}@${pipelineSuffix}`}
|
||||
shouldNotCreateHref
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.customButtonLabel',
|
||||
{ defaultMessage: 'Edit pipeline' }
|
||||
)}
|
||||
</EuiButtonEmptyTo>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s" color="subdued" grow={false}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.customDescription',
|
||||
{
|
||||
defaultMessage: 'Custom ingest pipeline for {indexName}',
|
||||
values: { indexName },
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.processorsDescription',
|
||||
{
|
||||
defaultMessage: '{processorsCount} Processors',
|
||||
values: { processorsCount },
|
||||
}
|
||||
)}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DEFAULT_PIPELINE_NAME } from '../../../../../../common/constants';
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../../common/types/connectors';
|
||||
|
||||
import { CurlRequest } from '../components/curl_request/curl_request';
|
||||
|
||||
import { PipelineSettingsForm } from './pipeline_settings_form';
|
||||
|
||||
interface IngestPipelineModalProps {
|
||||
closeModal: () => void;
|
||||
createCustomPipelines: () => void;
|
||||
displayOnly: boolean;
|
||||
indexName: string;
|
||||
isGated: boolean;
|
||||
isLoading: boolean;
|
||||
pipeline: IngestPipelineParams;
|
||||
savePipeline: () => void;
|
||||
setPipeline: (pipeline: IngestPipelineParams) => void;
|
||||
showModal: boolean;
|
||||
}
|
||||
|
||||
export const IngestPipelineModal: React.FC<IngestPipelineModalProps> = ({
|
||||
closeModal,
|
||||
createCustomPipelines,
|
||||
displayOnly,
|
||||
indexName,
|
||||
isGated,
|
||||
isLoading,
|
||||
pipeline,
|
||||
savePipeline,
|
||||
setPipeline,
|
||||
showModal,
|
||||
}) => {
|
||||
const { name } = pipeline;
|
||||
|
||||
// can't customize if you already have a custom pipeline!
|
||||
const canCustomize = name === DEFAULT_PIPELINE_NAME;
|
||||
|
||||
return showModal ? (
|
||||
<EuiModal onClose={closeModal}>
|
||||
<EuiModalHeader>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiModalHeaderTitle>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalHeaderTitle',
|
||||
{
|
||||
defaultMessage: 'Pipeline settings',
|
||||
}
|
||||
)}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued">
|
||||
<strong>{name}</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" grow={false} size="s">
|
||||
{displayOnly
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyAPIText',
|
||||
{
|
||||
defaultMessage:
|
||||
'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search. To use this configuration on API-based indices you can use the sample cURL request below.',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyConnectorText',
|
||||
{
|
||||
defaultMessage:
|
||||
'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem>
|
||||
<EuiLink href="TODO TODO TODO: Insert actual docslink" external>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalIngestLinkLabel',
|
||||
{
|
||||
defaultMessage: 'Learn more about Enterprise Search ingest pipelines',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiFlexItem>
|
||||
<EuiForm aria-labelledby="ingestPipelineHeader">
|
||||
<EuiFormRow>
|
||||
<EuiText size="m" id="ingestPipelineHeader">
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.formHeader',
|
||||
{
|
||||
defaultMessage: 'Optimize your content for search',
|
||||
}
|
||||
)}
|
||||
</strong>
|
||||
</EuiText>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow>
|
||||
<PipelineSettingsForm pipeline={pipeline} setPipeline={setPipeline} />
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiFlexItem>
|
||||
{displayOnly && (
|
||||
<>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m" id="ingestPipelineHeader" grow={false}>
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.curlHeader',
|
||||
{
|
||||
defaultMessage: 'Sample cURL request',
|
||||
}
|
||||
)}
|
||||
</strong>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<CurlRequest
|
||||
document={{ body: 'body', title: 'Title' }}
|
||||
indexName={indexName}
|
||||
pipeline={pipeline}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
{canCustomize && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s" grow={false}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.platinumText',
|
||||
{
|
||||
defaultMessage:
|
||||
'With a platinum license, you can create an index-specific version of this configuration and modify it for your use case.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="flexStart">
|
||||
<EuiButtonEmpty
|
||||
disabled={isGated}
|
||||
iconType={isGated ? 'lock' : undefined}
|
||||
onClick={createCustomPipelines}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.copyButtonLabel',
|
||||
{ defaultMessage: 'Copy and customize' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
{displayOnly ? (
|
||||
<EuiButton fill onClick={closeModal}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.closeButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<>
|
||||
<EuiButtonEmpty onClick={closeModal}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.cancelButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton fill onClick={savePipeline} isLoading={isLoading}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.saveButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Save',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</>
|
||||
)}
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
EuiButtonEmpty,
|
||||
EuiAccordion,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
|
||||
import { LicensingLogic } from '../../../../shared/licensing';
|
||||
import { CreateCustomPipelineApiLogic } from '../../../api/index/create_custom_pipeline_api_logic';
|
||||
import { FetchCustomPipelineApiLogic } from '../../../api/index/fetch_custom_pipeline_api_logic';
|
||||
import { CurlRequest } from '../components/curl_request/curl_request';
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
|
||||
import { CustomPipelinePanel } from './custom_pipeline_panel';
|
||||
import { IngestPipelineModal } from './ingest_pipeline_modal';
|
||||
import { PipelinesLogic } from './pipelines_logic';
|
||||
|
||||
export const IngestPipelinesCard: React.FC = () => {
|
||||
const { indexName } = useValues(IndexViewLogic);
|
||||
|
||||
const { canSetPipeline, pipelineState, showModal } = useValues(PipelinesLogic);
|
||||
const { closeModal, openModal, setPipelineState, savePipeline } = useActions(PipelinesLogic);
|
||||
const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic);
|
||||
const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic);
|
||||
const { data: customPipelines } = useValues(FetchCustomPipelineApiLogic);
|
||||
const { isCloud } = useValues(KibanaLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
|
||||
const isGated = !isCloud && !hasPlatinumLicense;
|
||||
const customPipeline = customPipelines ? customPipelines[`${indexName}@custom`] : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
fetchCustomPipeline({ indexName });
|
||||
}, [indexName]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<IngestPipelineModal
|
||||
closeModal={closeModal}
|
||||
createCustomPipelines={() => createCustomPipeline({ indexName })}
|
||||
displayOnly={!canSetPipeline}
|
||||
indexName={indexName}
|
||||
isGated={isGated}
|
||||
isLoading={false}
|
||||
pipeline={pipelineState}
|
||||
savePipeline={savePipeline}
|
||||
setPipeline={setPipelineState}
|
||||
showModal={showModal}
|
||||
/>
|
||||
{customPipeline && (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="primary">
|
||||
<CustomPipelinePanel
|
||||
indexName={indexName}
|
||||
pipelineSuffix="custom"
|
||||
processorsCount={customPipeline.processors?.length ?? 0}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="subdued">
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h4>{pipelineState.name}</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={openModal}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.settings.label',
|
||||
{ defaultMessage: 'Settings' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiAccordion
|
||||
buttonContent={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.accordion.label',
|
||||
{ defaultMessage: 'View sample cURL request' }
|
||||
)}
|
||||
id="ingestPipelinesCurlAccordion"
|
||||
>
|
||||
<CurlRequest
|
||||
document={{ body: 'body', title: 'Title' }}
|
||||
indexName={indexName}
|
||||
pipeline={pipelineState}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.managedBadge.label',
|
||||
{ defaultMessage: 'Managed' }
|
||||
)}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../../common/types/connectors';
|
||||
import { SettingsCheckableCard } from '../../shared/settings_checkable_card/settings_checkable_card';
|
||||
|
||||
interface PipelineSettingsFormProps {
|
||||
pipeline: IngestPipelineParams;
|
||||
setPipeline: (pipeline: IngestPipelineParams) => void;
|
||||
}
|
||||
|
||||
export const PipelineSettingsForm: React.FC<PipelineSettingsFormProps> = ({
|
||||
setPipeline,
|
||||
pipeline,
|
||||
}) => {
|
||||
const {
|
||||
extract_binary_content: extractBinaryContent,
|
||||
reduce_whitespace: reduceWhitespace,
|
||||
run_ml_inference: runMLInference,
|
||||
} = pipeline;
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<SettingsCheckableCard
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.extractBinaryDescription',
|
||||
{
|
||||
defaultMessage: 'Extract content from images and PDF files',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.extractBinaryLabel',
|
||||
{
|
||||
defaultMessage: 'Content extraction',
|
||||
}
|
||||
)}
|
||||
onChange={() =>
|
||||
setPipeline({
|
||||
...pipeline,
|
||||
extract_binary_content: !pipeline.extract_binary_content,
|
||||
})
|
||||
}
|
||||
checked={extractBinaryContent}
|
||||
id="ingestPipelineExtractBinaryContent"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<SettingsCheckableCard
|
||||
id="ingestPipelineReduceWhitespace"
|
||||
checked={reduceWhitespace}
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription',
|
||||
{
|
||||
defaultMessage: 'Trim extra whitespace from your documents automatically',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel',
|
||||
{
|
||||
defaultMessage: 'Reduce whitespace',
|
||||
}
|
||||
)}
|
||||
onChange={() =>
|
||||
setPipeline({ ...pipeline, reduce_whitespace: !pipeline.reduce_whitespace })
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<SettingsCheckableCard
|
||||
id="ingestPipelineRunMlInference"
|
||||
checked={runMLInference}
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition',
|
||||
{
|
||||
defaultMessage: 'Enhance your data using compatible trained ML models',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.settings.mlInferenceLabel',
|
||||
{
|
||||
defaultMessage: 'ML Inference Pipelines',
|
||||
}
|
||||
)}
|
||||
onChange={() =>
|
||||
setPipeline({ ...pipeline, run_ml_inference: !pipeline.run_ml_inference })
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -15,6 +15,7 @@ import { InferencePipeline } from '../../../../../../common/types/pipelines';
|
|||
import { DataPanel } from '../../../../shared/data_panel/data_panel';
|
||||
|
||||
import { InferencePipelineCard } from './inference_pipeline_card';
|
||||
import { IngestPipelinesCard } from './ingest_pipelines_card';
|
||||
|
||||
export const SearchIndexPipelines: React.FC = () => {
|
||||
// TODO: REPLACE THIS DATA WITH REAL DATA
|
||||
|
@ -75,7 +76,7 @@ export const SearchIndexPipelines: React.FC = () => {
|
|||
)}
|
||||
iconType="logstashInput"
|
||||
>
|
||||
<div />
|
||||
<IngestPipelinesCard />
|
||||
</DataPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogicMounter, mockFlashMessageHelpers } from '../../../../__mocks__/kea_logic';
|
||||
import { connectorIndex } from '../../../__mocks__/view_index.mock';
|
||||
|
||||
import { UpdatePipelineApiLogic } from '../../../api/connector/update_pipeline_api_logic';
|
||||
import { FetchIndexApiLogic } from '../../../api/index/fetch_index_api_logic';
|
||||
|
||||
import { PipelinesLogic } from './pipelines_logic';
|
||||
|
||||
const DEFAULT_PIPELINE_VALUES = {
|
||||
extract_binary_content: true,
|
||||
name: 'ent-search-generic-ingestion',
|
||||
reduce_whitespace: true,
|
||||
run_ml_inference: false,
|
||||
};
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
canSetPipeline: true,
|
||||
defaultPipelineValues: DEFAULT_PIPELINE_VALUES,
|
||||
defaultPipelineValuesData: undefined,
|
||||
index: undefined,
|
||||
pipelineState: DEFAULT_PIPELINE_VALUES,
|
||||
showModal: false,
|
||||
};
|
||||
|
||||
describe('PipelinesLogic', () => {
|
||||
const { mount } = new LogicMounter(PipelinesLogic);
|
||||
const { mount: mountFetchIndexApiLogic } = new LogicMounter(FetchIndexApiLogic);
|
||||
const { mount: mountUpdatePipelineLogic } = new LogicMounter(UpdatePipelineApiLogic);
|
||||
const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers;
|
||||
|
||||
const newPipeline = {
|
||||
...DEFAULT_PIPELINE_VALUES,
|
||||
name: 'new_pipeline_name',
|
||||
run_ml_inference: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mountFetchIndexApiLogic();
|
||||
mountUpdatePipelineLogic();
|
||||
mount();
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
expect(PipelinesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('should set showModal to false and call fetchApiSuccess', async () => {
|
||||
FetchIndexApiLogic.actions.apiSuccess(connectorIndex);
|
||||
PipelinesLogic.actions.fetchIndexApiSuccess = jest.fn();
|
||||
PipelinesLogic.actions.setPipelineState(newPipeline);
|
||||
PipelinesLogic.actions.openModal();
|
||||
PipelinesLogic.actions.apiSuccess({ connectorId: 'a', pipeline: newPipeline });
|
||||
expect(PipelinesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
index: {
|
||||
...connectorIndex,
|
||||
connector: { ...connectorIndex.connector },
|
||||
},
|
||||
});
|
||||
expect(flashSuccessToast).toHaveBeenCalled();
|
||||
expect(PipelinesLogic.actions.fetchIndexApiSuccess).toHaveBeenCalledWith({
|
||||
...connectorIndex,
|
||||
connector: {
|
||||
...connectorIndex.connector,
|
||||
pipeline: newPipeline,
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should set pipelineState on setPipeline', () => {
|
||||
PipelinesLogic.actions.setPipelineState({
|
||||
...DEFAULT_PIPELINE_VALUES,
|
||||
name: 'new_pipeline_name',
|
||||
});
|
||||
expect(PipelinesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' },
|
||||
});
|
||||
});
|
||||
describe('makeRequest', () => {
|
||||
it('should call clearFlashMessages', () => {
|
||||
PipelinesLogic.actions.makeRequest({ connectorId: 'a', pipeline: DEFAULT_PIPELINE_VALUES });
|
||||
expect(clearFlashMessages).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('openModal', () => {
|
||||
it('should set showModal to true', () => {
|
||||
PipelinesLogic.actions.openModal();
|
||||
expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, showModal: true });
|
||||
});
|
||||
});
|
||||
describe('closeModal', () => {
|
||||
it('should set showModal to false', () => {
|
||||
PipelinesLogic.actions.openModal();
|
||||
PipelinesLogic.actions.closeModal();
|
||||
expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, showModal: false });
|
||||
});
|
||||
});
|
||||
describe('apiError', () => {
|
||||
it('should call flashAPIError', () => {
|
||||
PipelinesLogic.actions.apiError('error' as any);
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
describe('apiSuccess', () => {
|
||||
it('should call flashSuccessToast', () => {
|
||||
PipelinesLogic.actions.apiSuccess({ connectorId: 'a', pipeline: newPipeline });
|
||||
expect(flashSuccessToast).toHaveBeenCalledWith('Pipelines successfully updated');
|
||||
});
|
||||
});
|
||||
describe('createCustomPipelineError', () => {
|
||||
it('should call flashAPIError', () => {
|
||||
PipelinesLogic.actions.createCustomPipelineError('error' as any);
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
describe('createCustomPipelineSuccess', () => {
|
||||
it('should call flashSuccessToast', () => {
|
||||
PipelinesLogic.actions.setPipelineState = jest.fn();
|
||||
PipelinesLogic.actions.savePipeline = jest.fn();
|
||||
PipelinesLogic.actions.fetchCustomPipeline = jest.fn();
|
||||
PipelinesLogic.actions.fetchIndexApiSuccess(connectorIndex);
|
||||
PipelinesLogic.actions.createCustomPipelineSuccess({ created: ['a', 'b'] });
|
||||
expect(flashSuccessToast).toHaveBeenCalledWith('Custom pipeline successfully created');
|
||||
expect(PipelinesLogic.actions.setPipelineState).toHaveBeenCalledWith({
|
||||
...PipelinesLogic.values.pipelineState,
|
||||
name: 'a',
|
||||
});
|
||||
expect(PipelinesLogic.actions.savePipeline).toHaveBeenCalled();
|
||||
expect(PipelinesLogic.actions.fetchCustomPipeline).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('fetchIndexApiSuccess', () => {
|
||||
it('should set pipelineState if not editing', () => {
|
||||
PipelinesLogic.actions.fetchIndexApiSuccess({
|
||||
...connectorIndex,
|
||||
connector: { ...connectorIndex.connector, pipeline: newPipeline },
|
||||
});
|
||||
expect(PipelinesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
index: {
|
||||
...connectorIndex,
|
||||
connector: { ...connectorIndex.connector, pipeline: newPipeline },
|
||||
},
|
||||
pipelineState: newPipeline,
|
||||
});
|
||||
});
|
||||
it('should not set configState if modal is open', () => {
|
||||
PipelinesLogic.actions.openModal();
|
||||
PipelinesLogic.actions.fetchIndexApiSuccess({
|
||||
...connectorIndex,
|
||||
connector: { ...connectorIndex.connector, pipeline: newPipeline },
|
||||
});
|
||||
expect(PipelinesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
index: {
|
||||
...connectorIndex,
|
||||
connector: { ...connectorIndex.connector, pipeline: newPipeline },
|
||||
},
|
||||
showModal: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('savePipeline', () => {
|
||||
it('should call makeRequest', () => {
|
||||
PipelinesLogic.actions.makeRequest = jest.fn();
|
||||
PipelinesLogic.actions.fetchIndexApiSuccess(connectorIndex);
|
||||
PipelinesLogic.actions.savePipeline();
|
||||
expect(PipelinesLogic.actions.makeRequest).toHaveBeenCalledWith({
|
||||
connectorId: '2',
|
||||
pipeline: DEFAULT_PIPELINE_VALUES,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DEFAULT_PIPELINE_VALUES } from '../../../../../../common/constants';
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../../common/types/connectors';
|
||||
import { ElasticsearchIndexWithIngestion } from '../../../../../../common/types/indices';
|
||||
import { Actions } from '../../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
clearFlashMessages,
|
||||
flashAPIErrors,
|
||||
flashSuccessToast,
|
||||
} from '../../../../shared/flash_messages';
|
||||
|
||||
import {
|
||||
FetchDefaultPipelineApiLogic,
|
||||
FetchDefaultPipelineResponse,
|
||||
} from '../../../api/connector/get_default_pipeline_api_logic';
|
||||
import {
|
||||
PostPipelineArgs,
|
||||
PostPipelineResponse,
|
||||
UpdatePipelineApiLogic,
|
||||
} from '../../../api/connector/update_pipeline_api_logic';
|
||||
import {
|
||||
CreateCustomPipelineApiLogic,
|
||||
CreateCustomPipelineApiLogicArgs,
|
||||
CreateCustomPipelineApiLogicResponse,
|
||||
} from '../../../api/index/create_custom_pipeline_api_logic';
|
||||
import {
|
||||
FetchCustomPipelineApiLogicArgs,
|
||||
FetchCustomPipelineApiLogicResponse,
|
||||
FetchCustomPipelineApiLogic,
|
||||
} from '../../../api/index/fetch_custom_pipeline_api_logic';
|
||||
import {
|
||||
FetchIndexApiLogic,
|
||||
FetchIndexApiParams,
|
||||
FetchIndexApiResponse,
|
||||
} from '../../../api/index/fetch_index_api_logic';
|
||||
import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../../utils/indices';
|
||||
|
||||
type PipelinesActions = Pick<
|
||||
Actions<PostPipelineArgs, PostPipelineResponse>,
|
||||
'apiError' | 'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
closeModal: () => void;
|
||||
createCustomPipeline: Actions<
|
||||
CreateCustomPipelineApiLogicArgs,
|
||||
CreateCustomPipelineApiLogicResponse
|
||||
>['makeRequest'];
|
||||
createCustomPipelineError: Actions<
|
||||
CreateCustomPipelineApiLogicArgs,
|
||||
CreateCustomPipelineApiLogicResponse
|
||||
>['apiError'];
|
||||
createCustomPipelineSuccess: Actions<
|
||||
CreateCustomPipelineApiLogicArgs,
|
||||
CreateCustomPipelineApiLogicResponse
|
||||
>['apiSuccess'];
|
||||
fetchCustomPipeline: Actions<
|
||||
FetchCustomPipelineApiLogicArgs,
|
||||
FetchCustomPipelineApiLogicResponse
|
||||
>['makeRequest'];
|
||||
fetchDefaultPipeline: Actions<undefined, FetchDefaultPipelineResponse>['makeRequest'];
|
||||
fetchDefaultPipelineSuccess: Actions<undefined, FetchDefaultPipelineResponse>['apiSuccess'];
|
||||
fetchIndexApiSuccess: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiSuccess'];
|
||||
openModal: () => void;
|
||||
savePipeline: () => void;
|
||||
setPipelineState(pipeline: IngestPipelineParams): {
|
||||
pipeline: IngestPipelineParams;
|
||||
};
|
||||
};
|
||||
|
||||
interface PipelinesValues {
|
||||
canSetPipeline: boolean;
|
||||
defaultPipelineValues: IngestPipelineParams;
|
||||
defaultPipelineValuesData: IngestPipelineParams | null;
|
||||
index: FetchIndexApiResponse;
|
||||
pipelineState: IngestPipelineParams;
|
||||
showModal: boolean;
|
||||
}
|
||||
|
||||
export const PipelinesLogic = kea<MakeLogicType<PipelinesValues, PipelinesActions>>({
|
||||
actions: {
|
||||
closeModal: true,
|
||||
openModal: true,
|
||||
savePipeline: true,
|
||||
setPipelineState: (pipeline: IngestPipelineParams) => ({ pipeline }),
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
CreateCustomPipelineApiLogic,
|
||||
[
|
||||
'apiError as createCustomPipelineError',
|
||||
'apiSuccess as createCustomPipelineSuccess',
|
||||
'makeRequest as createCustomPipeline',
|
||||
],
|
||||
UpdatePipelineApiLogic,
|
||||
['apiSuccess', 'apiError', 'makeRequest'],
|
||||
FetchIndexApiLogic,
|
||||
['apiSuccess as fetchIndexApiSuccess'],
|
||||
FetchDefaultPipelineApiLogic,
|
||||
['apiSuccess as fetchDefaultPipelineSuccess', 'makeRequest as fetchDefaultPipeline'],
|
||||
FetchCustomPipelineApiLogic,
|
||||
['makeRequest as fetchCustomPipeline'],
|
||||
],
|
||||
values: [
|
||||
FetchDefaultPipelineApiLogic,
|
||||
['data as defaultPipelineValuesData'],
|
||||
FetchIndexApiLogic,
|
||||
['data as index'],
|
||||
],
|
||||
},
|
||||
events: ({ actions, values }) => ({
|
||||
afterMount: () => {
|
||||
actions.fetchDefaultPipeline(undefined);
|
||||
actions.setPipelineState(
|
||||
isConnectorIndex(values.index) || isCrawlerIndex(values.index)
|
||||
? values.index.connector?.pipeline ?? values.defaultPipelineValues
|
||||
: values.defaultPipelineValues
|
||||
);
|
||||
},
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
apiError: (error) => flashAPIErrors(error),
|
||||
apiSuccess: ({ pipeline }) => {
|
||||
if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) {
|
||||
if (values.index.connector) {
|
||||
// had to split up these if checks rather than nest them or typescript wouldn't recognize connector as defined
|
||||
actions.fetchIndexApiSuccess({
|
||||
...values.index,
|
||||
connector: { ...values.index.connector, pipeline },
|
||||
});
|
||||
}
|
||||
}
|
||||
flashSuccessToast(
|
||||
i18n.translate('xpack.enterpriseSearch.content.indices.pipelines.successToast.title', {
|
||||
defaultMessage: 'Pipelines successfully updated',
|
||||
})
|
||||
);
|
||||
},
|
||||
closeModal: () =>
|
||||
actions.setPipelineState(
|
||||
isConnectorIndex(values.index) || isCrawlerIndex(values.index)
|
||||
? values.index.connector?.pipeline ?? values.defaultPipelineValues
|
||||
: values.defaultPipelineValues
|
||||
),
|
||||
createCustomPipelineError: (error) => flashAPIErrors(error),
|
||||
createCustomPipelineSuccess: ({ created }) => {
|
||||
flashSuccessToast(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.successToastCustom.title',
|
||||
{
|
||||
defaultMessage: 'Custom pipeline successfully created',
|
||||
}
|
||||
)
|
||||
);
|
||||
actions.setPipelineState({ ...values.pipelineState, name: created[0] });
|
||||
actions.savePipeline();
|
||||
actions.fetchCustomPipeline({ indexName: values.index.name });
|
||||
},
|
||||
fetchIndexApiSuccess: (index) => {
|
||||
if (!values.showModal) {
|
||||
// Don't do this when the modal is open to avoid overwriting the values while editing
|
||||
const pipeline =
|
||||
isConnectorIndex(index) || isCrawlerIndex(index)
|
||||
? index.connector?.pipeline
|
||||
: values.defaultPipelineValues;
|
||||
actions.setPipelineState(pipeline ?? values.defaultPipelineValues);
|
||||
}
|
||||
},
|
||||
makeRequest: () => clearFlashMessages(),
|
||||
openModal: () => {
|
||||
const pipeline =
|
||||
isCrawlerIndex(values.index) || isConnectorIndex(values.index)
|
||||
? values.index.connector?.pipeline
|
||||
: values.defaultPipelineValues;
|
||||
actions.setPipelineState(pipeline ?? values.defaultPipelineValues);
|
||||
},
|
||||
savePipeline: () => {
|
||||
if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) {
|
||||
if (values.index.connector) {
|
||||
actions.makeRequest({
|
||||
connectorId: values.index.connector?.id,
|
||||
pipeline: values.pipelineState,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'pipelines'],
|
||||
reducers: () => ({
|
||||
pipelineState: [
|
||||
DEFAULT_PIPELINE_VALUES,
|
||||
{
|
||||
setPipelineState: (_, { pipeline }) => pipeline,
|
||||
},
|
||||
],
|
||||
showModal: [
|
||||
false,
|
||||
{
|
||||
apiSuccess: () => false,
|
||||
closeModal: () => false,
|
||||
openModal: () => true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
canSetPipeline: [
|
||||
() => [selectors.index],
|
||||
(index: ElasticsearchIndexWithIngestion) => !isApiIndex(index),
|
||||
],
|
||||
defaultPipelineValues: [
|
||||
() => [selectors.defaultPipelineValuesData],
|
||||
(pipeline: IngestPipelineParams | null) => pipeline ?? DEFAULT_PIPELINE_VALUES,
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -14,10 +14,8 @@ import { useValues } from 'kea';
|
|||
import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { Status } from '../../../../../common/types/api';
|
||||
import { enableIndexPipelinesTab } from '../../../../../common/ui_settings_keys';
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { FetchIndexApiLogic } from '../../api/index/fetch_index_api_logic';
|
||||
|
@ -60,14 +58,9 @@ export const SearchIndex: React.FC = () => {
|
|||
const { tabId = SearchIndexTabId.OVERVIEW } = useParams<{
|
||||
tabId?: string;
|
||||
}>();
|
||||
const {
|
||||
services: { uiSettings },
|
||||
} = useKibana();
|
||||
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
|
||||
const pipelinesEnabled = uiSettings?.get<boolean>(enableIndexPipelinesTab) ?? false;
|
||||
|
||||
const ALL_INDICES_TABS: EuiTabbedContentTab[] = [
|
||||
{
|
||||
content: <SearchIndexOverview />,
|
||||
|
@ -126,21 +119,19 @@ export const SearchIndex: React.FC = () => {
|
|||
},
|
||||
];
|
||||
|
||||
const PIPELINES_TAB: EuiTabbedContentTab[] = [
|
||||
{
|
||||
content: <SearchIndexPipelines />,
|
||||
id: SearchIndexTabId.PIPELINES,
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndex.pipelinesTabLabel', {
|
||||
defaultMessage: 'Pipelines',
|
||||
}),
|
||||
},
|
||||
];
|
||||
const PIPELINES_TAB: EuiTabbedContentTab = {
|
||||
content: <SearchIndexPipelines />,
|
||||
id: SearchIndexTabId.PIPELINES,
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndex.pipelinesTabLabel', {
|
||||
defaultMessage: 'Pipelines',
|
||||
}),
|
||||
};
|
||||
|
||||
const tabs: EuiTabbedContentTab[] = [
|
||||
...ALL_INDICES_TABS,
|
||||
...(isConnectorIndex(indexData) ? CONNECTOR_TABS : []),
|
||||
...(isCrawlerIndex(indexData) ? CRAWLER_TABS : []),
|
||||
...(pipelinesEnabled ? PIPELINES_TAB : []),
|
||||
PIPELINES_TAB,
|
||||
];
|
||||
|
||||
const selectedTab = tabs.find((tab) => tab.id === tabId);
|
||||
|
|
|
@ -7,11 +7,26 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiButton, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
|
||||
|
||||
import { SettingsLogic } from './settings_logic';
|
||||
import { SettingsPanel } from './settings_panel';
|
||||
|
||||
export const Settings: React.FC = () => {
|
||||
const { makeRequest, setPipeline } = useActions(SettingsLogic);
|
||||
const { defaultPipeline, hasNoChanges, isLoading, pipelineState } = useValues(SettingsLogic);
|
||||
|
||||
const {
|
||||
extract_binary_content: extractBinaryContent,
|
||||
reduce_whitespace: reduceWhitespace,
|
||||
run_ml_inference: runMLInference,
|
||||
} = pipelineState;
|
||||
|
||||
return (
|
||||
<EnterpriseSearchContentPageTemplate
|
||||
pageChrome={[
|
||||
|
@ -22,10 +37,121 @@ export const Settings: React.FC = () => {
|
|||
defaultMessage: 'Settings',
|
||||
}),
|
||||
]}
|
||||
pageHeader={{
|
||||
pageTitle: i18n.translate('xpack.enterpriseSearch.content.settings.headerTitle', {
|
||||
defaultMessage: 'Content Settings',
|
||||
}),
|
||||
rightSideItems: [
|
||||
<EuiButton
|
||||
fill
|
||||
disabled={hasNoChanges}
|
||||
isLoading={isLoading}
|
||||
onClick={() => makeRequest(pipelineState)}
|
||||
>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.settings.saveButtonLabel', {
|
||||
defaultMessage: 'Save',
|
||||
})}
|
||||
</EuiButton>,
|
||||
<EuiButton
|
||||
disabled={hasNoChanges}
|
||||
isLoading={isLoading}
|
||||
onClick={() => setPipeline(defaultPipeline)}
|
||||
>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.settings.resetButtonLabel', {
|
||||
defaultMessage: 'Reset',
|
||||
})}
|
||||
</EuiButton>,
|
||||
],
|
||||
}}
|
||||
pageViewTelemetry="Settings"
|
||||
isLoading={false}
|
||||
>
|
||||
<>Settings</>
|
||||
<SettingsPanel
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.settings.contentExtraction.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Allow all ingestion mechanisms on your Enterprise Search deployment to extract searchable content from binary files, like PDFs and Word documents. This setting applies to all new Elasticsearch indices created by an Enterprise Search ingestion mechanism.',
|
||||
}
|
||||
)}
|
||||
link={
|
||||
<EuiLink href="TODO TODO TODO TODO" external>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.settings.contactExtraction.link', {
|
||||
defaultMessage: 'Learn more about content extraction',
|
||||
})}
|
||||
</EuiLink>
|
||||
}
|
||||
onChange={() =>
|
||||
setPipeline({
|
||||
...pipelineState,
|
||||
extract_binary_content: !pipelineState.extract_binary_content,
|
||||
})
|
||||
}
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.settings.contentExtraction.title', {
|
||||
defaultMessage: 'Deployment wide content extraction',
|
||||
})}
|
||||
value={extractBinaryContent}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<SettingsPanel
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.settings.whiteSpaceReduction.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Whitespace reduction will strip your full-text content of whitespace by default.',
|
||||
}
|
||||
)}
|
||||
link={
|
||||
<EuiLink href="TODO TODO TODO TODO" external>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.settings.whitespaceReduction.link', {
|
||||
defaultMessage: 'Learn more about whitespace reduction',
|
||||
})}
|
||||
</EuiLink>
|
||||
}
|
||||
onChange={() =>
|
||||
setPipeline({
|
||||
...pipelineState,
|
||||
reduce_whitespace: !pipelineState.reduce_whitespace,
|
||||
})
|
||||
}
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.settings.whitespaceReduction.deploymentHeaderTitle',
|
||||
{
|
||||
defaultMessage: 'Deployment wide whitespace reduction',
|
||||
}
|
||||
)}
|
||||
value={reduceWhitespace}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<SettingsPanel
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.settings.mlInference.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'ML Inference Pipelines will run as part of your pipelines. You will have to configure processors for each index individually on its pipelines page.',
|
||||
}
|
||||
)}
|
||||
link={
|
||||
<EuiLink href="TODO TODO TODO TODO" external>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.settings.mlInference.link', {
|
||||
defaultMessage: 'Learn more about content extraction',
|
||||
})}
|
||||
</EuiLink>
|
||||
}
|
||||
onChange={() =>
|
||||
setPipeline({
|
||||
...pipelineState,
|
||||
run_ml_inference: !pipelineState.run_ml_inference,
|
||||
})
|
||||
}
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.settings.mlInference.deploymentHeaderTitle',
|
||||
{
|
||||
defaultMessage: 'Deployment wide ML Inference Pipelines extraction',
|
||||
}
|
||||
)}
|
||||
value={runMLInference}
|
||||
/>
|
||||
</EnterpriseSearchContentPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { isDeepEqual } from 'react-use/lib/util';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DEFAULT_PIPELINE_VALUES } from '../../../../../common/constants';
|
||||
import { Status } from '../../../../../common/types/api';
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../common/types/connectors';
|
||||
import { Actions } from '../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
clearFlashMessages,
|
||||
flashAPIErrors,
|
||||
flashSuccessToast,
|
||||
} from '../../../shared/flash_messages';
|
||||
|
||||
import {
|
||||
FetchDefaultPipelineApiLogic,
|
||||
FetchDefaultPipelineResponse,
|
||||
} from '../../api/connector/get_default_pipeline_api_logic';
|
||||
import {
|
||||
PostDefaultPipelineArgs,
|
||||
PostDefaultPipelineResponse,
|
||||
UpdateDefaultPipelineApiLogic,
|
||||
} from '../../api/connector/update_default_pipeline_api_logic';
|
||||
|
||||
type PipelinesActions = Pick<
|
||||
Actions<PostDefaultPipelineArgs, PostDefaultPipelineResponse>,
|
||||
'apiError' | 'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
fetchDefaultPipeline: Actions<undefined, FetchDefaultPipelineResponse>['makeRequest'];
|
||||
fetchDefaultPipelineError: Actions<undefined, FetchDefaultPipelineResponse>['apiError'];
|
||||
fetchDefaultPipelineSuccess: Actions<undefined, FetchDefaultPipelineResponse>['apiSuccess'];
|
||||
setPipeline(pipeline: IngestPipelineParams): {
|
||||
pipeline: IngestPipelineParams;
|
||||
};
|
||||
};
|
||||
|
||||
interface PipelinesValues {
|
||||
defaultPipeline: IngestPipelineParams;
|
||||
fetchStatus: Status;
|
||||
hasNoChanges: boolean;
|
||||
isLoading: boolean;
|
||||
pipelineState: IngestPipelineParams;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
export const SettingsLogic = kea<MakeLogicType<PipelinesValues, PipelinesActions>>({
|
||||
actions: {
|
||||
setPipeline: (pipeline: IngestPipelineParams) => ({ pipeline }),
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
UpdateDefaultPipelineApiLogic,
|
||||
['apiSuccess', 'apiError', 'makeRequest'],
|
||||
FetchDefaultPipelineApiLogic,
|
||||
[
|
||||
'apiError as fetchDefaultPipelineError',
|
||||
'apiSuccess as fetchDefaultPipelineSuccess',
|
||||
'makeRequest as fetchDefaultPipeline',
|
||||
],
|
||||
],
|
||||
values: [
|
||||
FetchDefaultPipelineApiLogic,
|
||||
['data as defaultPipeline', 'status as fetchStatus'],
|
||||
UpdateDefaultPipelineApiLogic,
|
||||
['status'],
|
||||
],
|
||||
},
|
||||
events: ({ actions }) => ({
|
||||
afterMount: () => {
|
||||
actions.fetchDefaultPipeline(undefined);
|
||||
},
|
||||
}),
|
||||
listeners: ({ actions }) => ({
|
||||
apiError: (error) => flashAPIErrors(error),
|
||||
apiSuccess: (pipeline) => {
|
||||
flashSuccessToast(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.defaultPipelines.successToast.title',
|
||||
{
|
||||
defaultMessage: 'Default pipeline successfully updated',
|
||||
}
|
||||
)
|
||||
);
|
||||
actions.fetchDefaultPipelineSuccess(pipeline);
|
||||
},
|
||||
fetchDefaultPipelineSuccess: (pipeline) => {
|
||||
actions.setPipeline(pipeline);
|
||||
},
|
||||
makeRequest: () => clearFlashMessages(),
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'settings'],
|
||||
reducers: () => ({
|
||||
pipelineState: [
|
||||
DEFAULT_PIPELINE_VALUES,
|
||||
{
|
||||
setPipeline: (_, { pipeline }) => pipeline,
|
||||
},
|
||||
],
|
||||
showModal: [
|
||||
false,
|
||||
{
|
||||
apiSuccess: () => false,
|
||||
closeModal: () => false,
|
||||
openModal: () => true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
hasNoChanges: [
|
||||
() => [selectors.pipelineState, selectors.defaultPipeline],
|
||||
(pipelineState: IngestPipelineParams, defaultPipeline: IngestPipelineParams) =>
|
||||
isDeepEqual(pipelineState, defaultPipeline),
|
||||
],
|
||||
isLoading: [
|
||||
() => [selectors.status, selectors.fetchStatus],
|
||||
(status, fetchStatus) =>
|
||||
[Status.LOADING, Status.IDLE].includes(fetchStatus) || status === Status.LOADING,
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiSplitPanel,
|
||||
EuiSwitch,
|
||||
EuiSwitchEvent,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
interface SettingsPanelProps {
|
||||
description: string;
|
||||
link: React.ReactNode;
|
||||
onChange: (event: EuiSwitchEvent) => void;
|
||||
title: string;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export const SettingsPanel: React.FC<SettingsPanelProps> = ({
|
||||
description,
|
||||
link,
|
||||
onChange,
|
||||
title,
|
||||
value,
|
||||
}) => (
|
||||
<EuiSplitPanel.Outer hasBorder grow>
|
||||
<EuiSplitPanel.Inner>
|
||||
<EuiText size="m">
|
||||
<h4>
|
||||
<strong>{title}</strong>
|
||||
</h4>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiText size="s">
|
||||
<p>{description}</p>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.settings.contentExtraction.descriptionTwo',
|
||||
{
|
||||
defaultMessage:
|
||||
'You can also enable or disable this feature for a specific index on the index’s configuration page.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiSplitPanel.Inner>
|
||||
<EuiSplitPanel.Inner grow={false} color="subdued">
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
checked={value}
|
||||
label={i18n.translate('xpack.enterpriseSearch.content.settings.extractBinaryLabel', {
|
||||
defaultMessage: 'Content extraction',
|
||||
})}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{link}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiSplitPanel.Inner>
|
||||
</EuiSplitPanel.Outer>
|
||||
);
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiCheckableCard, EuiText, EuiTitle } from '@elastic/eui';
|
||||
|
||||
export const SettingsCheckableCard: React.FC<{
|
||||
checked: boolean;
|
||||
description: string;
|
||||
id: string;
|
||||
label: string;
|
||||
onChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||
}> = ({ checked, description, id, label, onChange }) => (
|
||||
<EuiCheckableCard
|
||||
label={
|
||||
<EuiTitle size="xs">
|
||||
<h4>{label}</h4>
|
||||
</EuiTitle>
|
||||
}
|
||||
checkableType="checkbox"
|
||||
onChange={onChange}
|
||||
checked={checked}
|
||||
id={id}
|
||||
>
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>{description}</p>
|
||||
</EuiText>
|
||||
</EuiCheckableCard>
|
||||
);
|
|
@ -41,6 +41,12 @@ describe('useEnterpriseSearchContentNav', () => {
|
|||
id: 'search_indices',
|
||||
name: 'Indices',
|
||||
},
|
||||
{
|
||||
href: '/app/enterprise_search/content/settings',
|
||||
id: 'settings',
|
||||
items: undefined,
|
||||
name: 'Settings',
|
||||
},
|
||||
],
|
||||
name: 'Content',
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
ENTERPRISE_SEARCH_OVERVIEW_PLUGIN,
|
||||
WORKPLACE_SEARCH_PLUGIN,
|
||||
} from '../../../../common/constants';
|
||||
import { SEARCH_INDICES_PATH } from '../../enterprise_search_content/routes';
|
||||
import { SEARCH_INDICES_PATH, SETTINGS_PATH } from '../../enterprise_search_content/routes';
|
||||
import { KibanaLogic } from '../kibana';
|
||||
|
||||
import { generateNavLink } from './nav_link_helpers';
|
||||
|
@ -51,6 +51,17 @@ export const useEnterpriseSearchNav = () => {
|
|||
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SEARCH_INDICES_PATH,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.contentSettingsTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SETTINGS_PATH,
|
||||
}),
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', {
|
||||
defaultMessage: 'Content',
|
||||
|
|
|
@ -39,6 +39,7 @@ export const config: PluginConfigDescriptor<ConfigType> = {
|
|||
schema: configSchema,
|
||||
};
|
||||
export const CONNECTORS_INDEX = '.elastic-connectors';
|
||||
export const CURRENT_CONNECTORS_INDEX = '.elastic-connectors-v1';
|
||||
export const CONNECTORS_JOBS_INDEX = '.elastic-connectors-sync-jobs';
|
||||
export const CONNECTORS_VERSION = '1';
|
||||
export const CRAWLERS_INDEX = '.ent-search-actastic-crawler2_configurations';
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
export const getCustomPipelines = async (
|
||||
indexName: string,
|
||||
client: IScopedClusterClient
|
||||
): Promise<IngestGetPipelineResponse> => {
|
||||
try {
|
||||
const pipelinesResponse = await client.asCurrentUser.ingest.getPipeline({
|
||||
id: `${indexName}*`,
|
||||
});
|
||||
|
||||
return pipelinesResponse;
|
||||
} catch (error) {
|
||||
// If we can't find anything, we return an empty object
|
||||
return {};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { CURRENT_CONNECTORS_INDEX } from '../..';
|
||||
import { DEFAULT_PIPELINE_VALUES } from '../../../common/constants';
|
||||
|
||||
import { IngestPipelineParams } from '../../../common/types/connectors';
|
||||
import { DefaultConnectorsPipelineMeta } from '../../index_management/setup_indices';
|
||||
|
||||
export const getDefaultPipeline = async (
|
||||
client: IScopedClusterClient
|
||||
): Promise<IngestPipelineParams> => {
|
||||
const mapping = await client.asCurrentUser.indices.getMapping({
|
||||
index: CURRENT_CONNECTORS_INDEX,
|
||||
});
|
||||
const meta: DefaultConnectorsPipelineMeta | undefined =
|
||||
mapping[CURRENT_CONNECTORS_INDEX]?.mappings._meta?.pipeline;
|
||||
const mappedMapping: IngestPipelineParams = meta
|
||||
? {
|
||||
extract_binary_content: meta.default_extract_binary_content,
|
||||
name: meta.default_name,
|
||||
reduce_whitespace: meta.default_reduce_whitespace,
|
||||
run_ml_inference: meta.default_run_ml_inference,
|
||||
}
|
||||
: DEFAULT_PIPELINE_VALUES;
|
||||
return mappedMapping;
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { CURRENT_CONNECTORS_INDEX } from '../..';
|
||||
|
||||
import { IngestPipelineParams } from '../../../common/types/connectors';
|
||||
import {
|
||||
DefaultConnectorsPipelineMeta,
|
||||
setupConnectorsIndices,
|
||||
} from '../../index_management/setup_indices';
|
||||
import { isIndexNotFoundException } from '../../utils/identify_exceptions';
|
||||
|
||||
export const updateDefaultPipeline = async (
|
||||
client: IScopedClusterClient,
|
||||
pipeline: IngestPipelineParams
|
||||
) => {
|
||||
try {
|
||||
const mapping = await client.asCurrentUser.indices.getMapping({
|
||||
index: CURRENT_CONNECTORS_INDEX,
|
||||
});
|
||||
const newPipeline: DefaultConnectorsPipelineMeta = {
|
||||
default_extract_binary_content: pipeline.extract_binary_content,
|
||||
default_name: pipeline.name,
|
||||
default_reduce_whitespace: pipeline.reduce_whitespace,
|
||||
default_run_ml_inference: pipeline.run_ml_inference,
|
||||
};
|
||||
await client.asCurrentUser.indices.putMapping({
|
||||
_meta: { ...mapping[CURRENT_CONNECTORS_INDEX].mappings._meta, pipeline: newPipeline },
|
||||
index: CURRENT_CONNECTORS_INDEX,
|
||||
});
|
||||
} catch (error) {
|
||||
if (isIndexNotFoundException(error)) {
|
||||
setupConnectorsIndices(client.asCurrentUser);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { CONNECTORS_INDEX } from '../..';
|
||||
|
||||
import { IngestPipelineParams } from '../../../common/types/connectors';
|
||||
|
||||
export const updateConnectorPipeline = async (
|
||||
client: IScopedClusterClient,
|
||||
connectorId: string,
|
||||
pipeline: IngestPipelineParams
|
||||
) => {
|
||||
await client.asCurrentUser.update({
|
||||
doc: { pipeline },
|
||||
id: connectorId,
|
||||
index: CONNECTORS_INDEX,
|
||||
});
|
||||
};
|
|
@ -14,6 +14,9 @@ import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs
|
|||
import { startConnectorSync } from '../../lib/connectors/start_sync';
|
||||
import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration';
|
||||
import { updateConnectorScheduling } from '../../lib/connectors/update_connector_scheduling';
|
||||
import { getDefaultPipeline } from '../../lib/pipelines/get_default_pipeline';
|
||||
import { updateDefaultPipeline } from '../../lib/pipelines/update_default_pipeline';
|
||||
import { updateConnectorPipeline } from '../../lib/pipelines/update_pipeline';
|
||||
|
||||
import { RouteDependencies } from '../../plugin';
|
||||
import { createError } from '../../utils/create_error';
|
||||
|
@ -137,4 +140,57 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
return response.ok({ body: result });
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
{
|
||||
path: '/internal/enterprise_search/connectors/{connectorId}/pipeline',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
extract_binary_content: schema.boolean(),
|
||||
name: schema.string(),
|
||||
reduce_whitespace: schema.boolean(),
|
||||
run_ml_inference: schema.boolean(),
|
||||
}),
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
await updateConnectorPipeline(client, request.params.connectorId, request.body);
|
||||
return response.ok();
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
{
|
||||
path: '/internal/enterprise_search/connectors/default_pipeline',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
extract_binary_content: schema.boolean(),
|
||||
name: schema.string(),
|
||||
reduce_whitespace: schema.boolean(),
|
||||
run_ml_inference: schema.boolean(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
await updateDefaultPipeline(client, request.body);
|
||||
return response.ok();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/enterprise_search/connectors/default_pipeline',
|
||||
validate: {},
|
||||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await getDefaultPipeline(client);
|
||||
return response.ok({ body: result });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ import { fetchIndex } from '../../lib/indices/fetch_index';
|
|||
import { fetchIndices } from '../../lib/indices/fetch_indices';
|
||||
import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors';
|
||||
import { generateApiKey } from '../../lib/indices/generate_api_key';
|
||||
import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions';
|
||||
import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines';
|
||||
import { RouteDependencies } from '../../plugin';
|
||||
import { createError } from '../../utils/create_error';
|
||||
import { createIndexPipelineDefinitions } from '../../utils/create_pipeline_definitions';
|
||||
import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler';
|
||||
import { isIndexNotFoundException } from '../../utils/identify_exceptions';
|
||||
|
||||
|
@ -266,6 +267,26 @@ export function registerIndexRoutes({
|
|||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/enterprise_search/indices/{indexName}/pipelines',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
indexName: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const indexName = decodeURIComponent(request.params.indexName);
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const pipelines = await getCustomPipelines(indexName, client);
|
||||
return response.ok({
|
||||
body: pipelines,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue