mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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>
This commit is contained in:
parent
fbe3aa36b3
commit
e44087a06d
10 changed files with 320 additions and 0 deletions
|
@ -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`,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<ConvertConnectorApiLogicResponse> => {
|
||||
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
|
||||
);
|
|
@ -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 && (
|
||||
<EuiConfirmModal
|
||||
onCancel={() => 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
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
{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.",
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiConfirmModal>
|
||||
)}
|
||||
<EuiFlexGroup direction="row" alignItems="center" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="wrench" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h4>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title',
|
||||
{
|
||||
defaultMessage: 'Customize your connector',
|
||||
}
|
||||
)}
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.description"
|
||||
defaultMessage="Want to customize this native connector? Convert it to a {link}, to be self-managed on your own infrastructure."
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink href={docLinks.buildConnector} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle',
|
||||
{ defaultMessage: 'connector client' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton onClick={() => showModal()}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle',
|
||||
{ defaultMessage: 'Convert connector' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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<ConvertConnectorApiLogicArgs, ConvertConnectorApiLogicResponse>,
|
||||
'apiError' | 'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
convertConnector(): void;
|
||||
fetchIndex(): IndexViewActions['fetchIndex'];
|
||||
hideModal(): void;
|
||||
showModal(): void;
|
||||
};
|
||||
|
||||
export const ConvertConnectorLogic = kea<
|
||||
MakeLogicType<ConvertConnectorValues, ConvertConnectorActions>
|
||||
>({
|
||||
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],
|
||||
}),
|
||||
});
|
|
@ -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 = () => {
|
|||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel hasBorder hasShadow={false}>
|
||||
<ConvertConnector />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Connector>({
|
||||
doc: {
|
||||
is_native: isNative,
|
||||
},
|
||||
id: connectorId,
|
||||
index: CONNECTORS_INDEX,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
|
@ -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();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue