From e44087a06d5660690b0e31646b5c902ab115d6eb Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 27 Apr 2023 19:23:14 +0200 Subject: [PATCH] [Enterprise Search] Enable converting native connector to custom (#155853) ## Summary This adds the ability to convert a native connector to a customized connector. --------- Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../convert_connector_api_logic.test.ts | 32 +++++ .../connector/convert_connector_api_logic.ts | 32 +++++ .../convert_connector.tsx | 115 ++++++++++++++++++ .../convert_connector_logic.tsx | 82 +++++++++++++ .../native_connector_configuration.tsx | 6 + .../shared/doc_links/doc_links.ts | 3 + .../lib/connectors/put_update_native.ts | 27 ++++ .../routes/enterprise_search/connectors.ts | 21 ++++ 10 files changed, 320 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx create mode 100644 x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 29ac5472e37dd..89b3610e622b9 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -126,6 +126,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { apiKeys: `${KIBANA_DOCS}api-keys.html`, behavioralAnalytics: `${ENTERPRISE_SEARCH_DOCS}analytics-overview.html`, behavioralAnalyticsEvents: `${ENTERPRISE_SEARCH_DOCS}analytics-events.html`, + buildConnector: `{$ENTERPRISE_SEARCH_DOCS}build-connector.html`, bulkApi: `${ELASTICSEARCH_DOCS}docs-bulk.html`, configuration: `${ENTERPRISE_SEARCH_DOCS}configuration.html`, connectors: `${ENTERPRISE_SEARCH_DOCS}connectors.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index efe5e95f238d0..5e2e8e7bf1304 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -111,6 +111,7 @@ export interface DocLinks { readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsEvents: string; + readonly buildConnector: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts new file mode 100644 index 0000000000000..01691488470f1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.test.ts @@ -0,0 +1,32 @@ +/* + * 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 { convertConnector } from './convert_connector_api_logic'; + +describe('ConvertConnectorApilogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('convertConnector', () => { + it('calls correct api', async () => { + const promise = Promise.resolve('result'); + http.put.mockReturnValue(promise); + const result = convertConnector({ connectorId: 'connectorId1' }); + await nextTick(); + expect(http.put).toHaveBeenCalledWith( + '/internal/enterprise_search/connectors/connectorId1/native', + { body: JSON.stringify({ is_native: false }) } + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts new file mode 100644 index 0000000000000..ba998c9dbce9d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts @@ -0,0 +1,32 @@ +/* + * 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 ConvertConnectorApiLogicArgs { + connectorId: string; +} + +export interface ConvertConnectorApiLogicResponse { + updated: boolean; +} + +export const convertConnector = async ({ + connectorId, +}: ConvertConnectorApiLogicArgs): Promise => { + const route = `/internal/enterprise_search/connectors/${connectorId}/native`; + + return await HttpLogic.values.http.put<{ updated: boolean }>(route, { + body: JSON.stringify({ is_native: false }), + }); +}; + +export const ConvertConnectorApiLogic = createApiLogic( + ['convert_connector_api_logic'], + convertConnector +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx new file mode 100644 index 0000000000000..450565088e842 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx @@ -0,0 +1,115 @@ +/* + * 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 { useActions, useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTitle, + EuiSpacer, + EuiText, + EuiLink, + EuiButton, + EuiConfirmModal, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; + +import { docLinks } from '../../../../../shared/doc_links'; + +import { ConvertConnectorLogic } from './convert_connector_logic'; + +export const ConvertConnector: React.FC = () => { + const { convertConnector, hideModal, showModal } = useActions(ConvertConnectorLogic); + const { isLoading, isModalVisible } = useValues(ConvertConnectorLogic); + + return ( + <> + {isModalVisible && ( + hideModal()} + onConfirm={() => convertConnector()} + title={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertInfexConfirm.title', + { defaultMessage: 'Sure you want to convert your connector?' } + )} + buttonColor="danger" + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.text', + { + defaultMessage: 'Yes', + } + )} + isLoading={isLoading} + defaultFocusedButton="confirm" + maxWidth + > + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.description', + { + defaultMessage: + "Once you convert a native connector to a self-managed connector client this can't be undone.", + } + )} +

+
+
+ )} + + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title', + { + defaultMessage: 'Customize your connector', + } + )} +

+
+
+
+ + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle', + { defaultMessage: 'connector client' } + )} + + ), + }} + /> + + showModal()}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle', + { defaultMessage: 'Convert connector' } + )} + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx new file mode 100644 index 0000000000000..841f8cde106a2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx @@ -0,0 +1,82 @@ +/* + * 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. + */ +/* + * 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 { Status } from '../../../../../../../common/types/api'; +import { Actions } from '../../../../../shared/api_logic/create_api_logic'; +import { + ConvertConnectorApiLogic, + ConvertConnectorApiLogicArgs, + ConvertConnectorApiLogicResponse, +} from '../../../../api/connector/convert_connector_api_logic'; +import { IndexViewActions, IndexViewLogic } from '../../index_view_logic'; + +interface ConvertConnectorValues { + connectorId: typeof IndexViewLogic.values.connectorId; + isLoading: boolean; + isModalVisible: boolean; + status: Status; +} + +type ConvertConnectorActions = Pick< + Actions, + 'apiError' | 'apiSuccess' | 'makeRequest' +> & { + convertConnector(): void; + fetchIndex(): IndexViewActions['fetchIndex']; + hideModal(): void; + showModal(): void; +}; + +export const ConvertConnectorLogic = kea< + MakeLogicType +>({ + actions: { + convertConnector: () => true, + deleteDomain: () => true, + hideModal: () => true, + showModal: () => true, + }, + connect: { + actions: [ + ConvertConnectorApiLogic, + ['apiError', 'apiSuccess', 'makeRequest'], + IndexViewLogic, + ['fetchIndex'], + ], + values: [ConvertConnectorApiLogic, ['status'], IndexViewLogic, ['connectorId']], + }, + listeners: ({ actions, values }) => ({ + convertConnector: () => { + if (values.connectorId) { + actions.makeRequest({ connectorId: values.connectorId }); + } + }, + }), + path: ['enterprise_search', 'convert_connector_modal'], + reducers: { + isModalVisible: [ + false, + { + apiError: () => false, + apiSuccess: () => false, + hideModal: () => false, + showModal: () => true, + }, + ], + }, + selectors: ({ selectors }) => ({ + isLoading: [() => [selectors.status], (status: Status) => status === Status.LOADING], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx index c4c4af581ef5a..960bd61264ddf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx @@ -31,6 +31,7 @@ import { IndexViewLogic } from '../../index_view_logic'; import { ConnectorNameAndDescription } from '../connector_name_and_description/connector_name_and_description'; import { NATIVE_CONNECTORS } from '../constants'; +import { ConvertConnector } from './convert_connector'; import { NativeConnectorAdvancedConfiguration } from './native_connector_advanced_configuration'; import { NativeConnectorConfigurationConfig } from './native_connector_configuration_config'; import { ResearchConfiguration } from './research_configuration'; @@ -203,6 +204,11 @@ export const NativeConnectorConfiguration: React.FC = () => { + + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 607dc7361e2f4..bd273957d2ffa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -36,6 +36,7 @@ class DocLinks { public appSearchWebCrawlerReference: string; public behavioralAnalytics: string; public behavioralAnalyticsEvents: string; + public buildConnector: string; public bulkApi: string; public clientsGoIndex: string; public clientsGuide: string; @@ -164,6 +165,7 @@ class DocLinks { this.appSearchWebCrawlerReference = ''; this.behavioralAnalytics = ''; this.behavioralAnalyticsEvents = ''; + this.buildConnector = ''; this.bulkApi = ''; this.clientsGoIndex = ''; this.clientsGuide = ''; @@ -294,6 +296,7 @@ class DocLinks { this.appSearchWebCrawlerReference = docLinks.links.appSearch.webCrawlerReference; this.behavioralAnalytics = docLinks.links.enterpriseSearch.behavioralAnalytics; this.behavioralAnalyticsEvents = docLinks.links.enterpriseSearch.behavioralAnalyticsEvents; + this.buildConnector = docLinks.links.enterpriseSearch.buildConnector; this.bulkApi = docLinks.links.enterpriseSearch.bulkApi; this.clientsGoIndex = docLinks.links.clients.goIndex; this.clientsGuide = docLinks.links.clients.guide; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts new file mode 100644 index 0000000000000..6b3039974f8a4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts @@ -0,0 +1,27 @@ +/* + * 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-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '../..'; +import { Connector } from '../../../common/types/connectors'; + +export const putUpdateNative = async ( + client: IScopedClusterClient, + connectorId: string, + isNative: boolean +) => { + const result = await client.asCurrentUser.update({ + doc: { + is_native: isNative, + }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + + return result; +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 32e7cb79a2597..f0c0df50f7155 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -22,6 +22,7 @@ import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs import { cancelSyncs } from '../../lib/connectors/post_cancel_syncs'; import { updateFiltering } from '../../lib/connectors/put_update_filtering'; import { updateFilteringDraft } from '../../lib/connectors/put_update_filtering_draft'; +import { putUpdateNative } from '../../lib/connectors/put_update_native'; import { startConnectorSync } from '../../lib/connectors/start_sync'; import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration'; import { updateConnectorNameAndDescription } from '../../lib/connectors/update_connector_name_and_description'; @@ -383,4 +384,24 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { return result ? response.ok({ body: result }) : response.conflict(); }) ); + router.put( + { + path: '/internal/enterprise_search/connectors/{connectorId}/native', + validate: { + body: schema.object({ + is_native: schema.boolean(), + }), + params: schema.object({ + connectorId: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const connectorId = decodeURIComponent(request.params.connectorId); + const { is_native } = request.body; + const result = await putUpdateNative(client, connectorId, is_native); + return result ? response.ok({ body: result }) : response.conflict(); + }) + ); }