mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Search] Create connectors page (#167804)
## Summary This adds the ability to create a connector to Serverless Search and edit its name, description and service type. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f377164948
commit
f0125245ed
16 changed files with 744 additions and 25 deletions
|
@ -21,8 +21,9 @@ export const createConnector = async (
|
|||
isNative: boolean;
|
||||
language: string | null;
|
||||
name?: string;
|
||||
pipeline: IngestPipelineParams;
|
||||
pipeline?: IngestPipelineParams;
|
||||
serviceType?: string | null;
|
||||
instant_response?: boolean;
|
||||
}
|
||||
): Promise<Connector> => {
|
||||
const document = createConnectorDocument({
|
||||
|
@ -33,7 +34,7 @@ export const createConnector = async (
|
|||
const result = await client.index({
|
||||
document,
|
||||
index: CURRENT_CONNECTORS_INDEX,
|
||||
refresh: 'wait_for',
|
||||
refresh: input.instant_response ? false : 'wait_for',
|
||||
});
|
||||
|
||||
return { ...document, id: result._id };
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { WriteResponseBase } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -17,7 +18,7 @@ export const updateConnectorNameAndDescription = async (
|
|||
client: ElasticsearchClient,
|
||||
connectorId: string,
|
||||
connectorUpdates: Partial<Pick<Connector, 'name' | 'description'>>
|
||||
) => {
|
||||
): Promise<WriteResponseBase> => {
|
||||
const connectorResult = await client.get<ConnectorDocument>({
|
||||
id: connectorId,
|
||||
index: CONNECTORS_INDEX,
|
||||
|
|
|
@ -15,6 +15,14 @@ export const CANCEL_LABEL: string = i18n.translate('xpack.serverlessSearch.cance
|
|||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
export const EDIT_LABEL: string = i18n.translate('xpack.serverlessSearch.edit', {
|
||||
defaultMessage: 'Edit',
|
||||
});
|
||||
|
||||
export const SAVE_LABEL: string = i18n.translate('xpack.serverlessSearch.save', {
|
||||
defaultMessage: 'Save',
|
||||
});
|
||||
|
||||
export const BACK_LABEL: string = i18n.translate('xpack.serverlessSearch.back', {
|
||||
defaultMessage: 'Back',
|
||||
});
|
||||
|
@ -45,3 +53,7 @@ export const INVALID_JSON_ERROR: string = i18n.translate(
|
|||
defaultMessage: 'Invalid JSON',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_LABEL: string = i18n.translate('xpack.serverlessSearch.connector', {
|
||||
defaultMessage: 'Connector',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPageTemplate,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CONNECTOR_LABEL } from '../../../../common/i18n_string';
|
||||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
import { BASE_CONNECTORS_PATH } from '../connectors_router';
|
||||
import { EditName } from './edit_name';
|
||||
import { EditServiceType } from './edit_service_type';
|
||||
import { EditDescription } from './edit_description';
|
||||
|
||||
export const EditConnector: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const {
|
||||
application: { navigateToUrl },
|
||||
http,
|
||||
} = useKibanaServices();
|
||||
|
||||
const { data, isLoading, refetch } = useQuery({
|
||||
queryKey: [`fetchConnector${id}`],
|
||||
queryFn: () =>
|
||||
http.fetch<{ connector: Connector }>(`/internal/serverless_search/connector/${id}`),
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchEditConnectorsPage">
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
title={
|
||||
<h1>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.loading', {
|
||||
defaultMessage: 'Loading',
|
||||
})}
|
||||
</h1>
|
||||
}
|
||||
/>
|
||||
</EuiPageTemplate>;
|
||||
}
|
||||
if (!data?.connector) {
|
||||
return (
|
||||
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchEditConnectorsPage">
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
title={
|
||||
<h1>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.notFound', {
|
||||
defaultMessage: 'Could not find a connector with id {id}',
|
||||
values: { id },
|
||||
})}
|
||||
</h1>
|
||||
}
|
||||
actions={
|
||||
<EuiButton color="primary" fill onClick={() => navigateToUrl(BASE_CONNECTORS_PATH)}>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.goBack', {
|
||||
defaultMessage: 'Go back',
|
||||
})}
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
const { connector } = data;
|
||||
|
||||
return (
|
||||
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchEditConnectorsPage">
|
||||
<EuiPageTemplate.Section grow={false}>
|
||||
<EuiText size="s">{CONNECTOR_LABEL}</EuiText>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EditName connectorId={id} name={connector.name} onSuccess={refetch} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.translate('xpack.serverlessSearch.connectors.openMenuLabel', {
|
||||
defaultMessage: 'Open menu',
|
||||
})}
|
||||
iconType="boxesVertical"
|
||||
/>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageTemplate.Section>
|
||||
<EuiPageTemplate.Section>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem>
|
||||
<EditServiceType
|
||||
connectorId={id}
|
||||
serviceType={connector.service_type ?? ''}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EditDescription
|
||||
connectorId={id}
|
||||
description={connector.description ?? ''}
|
||||
onSuccess={refetch}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageTemplate.Section>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiFieldText,
|
||||
EuiForm,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiFormRow,
|
||||
EuiText,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { CANCEL_LABEL, EDIT_LABEL, SAVE_LABEL } from '../../../../common/i18n_string';
|
||||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
|
||||
interface EditDescriptionProps {
|
||||
connectorId: string;
|
||||
description: string;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export const EditDescription: React.FC<EditDescriptionProps> = ({
|
||||
connectorId,
|
||||
description,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [newDescription, setNewDescription] = useState(description);
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
useEffect(() => setNewDescription(description), [description]);
|
||||
|
||||
const { isLoading, isSuccess, mutate } = useMutation({
|
||||
mutationFn: async (inputDescription: string) => {
|
||||
const body = { Description: inputDescription };
|
||||
const result = await http.post(
|
||||
`/internal/serverless_search/connectors/${connectorId}/description`,
|
||||
{
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
setIsEditing(false);
|
||||
onSuccess();
|
||||
}
|
||||
}, [isSuccess, onSuccess]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiForm>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow
|
||||
helpText={i18n.translate('xpack.serverlessSearch.connectors.descriptionHelpText', {
|
||||
defaultMessage: 'Optional description for your connector.',
|
||||
})}
|
||||
label={i18n.translate('xpack.serverlessSearch.connectors.descriptionLabel', {
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
labelAppend={
|
||||
<EuiButtonEmpty size="xs" onClick={() => setIsEditing(true)}>
|
||||
{EDIT_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
>
|
||||
{isEditing ? (
|
||||
<EuiFieldText
|
||||
onChange={(event) => setNewDescription(event.target.value)}
|
||||
value={newDescription}
|
||||
/>
|
||||
) : (
|
||||
<EuiText>{description}</EuiText>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{isEditing && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup direction="row" justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
onClick={() => mutate(newDescription)}
|
||||
type="submit"
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{SAVE_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiButton
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
setNewDescription(description);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
>
|
||||
{CANCEL_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiForm>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiTitle,
|
||||
EuiButtonIcon,
|
||||
EuiFieldText,
|
||||
EuiForm,
|
||||
EuiButton,
|
||||
EuiFormLabel,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { CANCEL_LABEL, CONNECTOR_LABEL, SAVE_LABEL } from '../../../../common/i18n_string';
|
||||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
|
||||
interface EditNameProps {
|
||||
connectorId: string;
|
||||
name: string;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export const EditName: React.FC<EditNameProps> = ({ connectorId, name, onSuccess }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [newName, setNewName] = useState(name);
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
useEffect(() => setNewName(name), [name]);
|
||||
|
||||
const { isLoading, isSuccess, mutate } = useMutation({
|
||||
mutationFn: async (inputName: string) => {
|
||||
const body = { name: inputName };
|
||||
const result = await http.post(`/internal/serverless_search/connectors/${connectorId}/name`, {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
setIsEditing(false);
|
||||
onSuccess();
|
||||
}
|
||||
}, [isSuccess, onSuccess]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="row">
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h1>{name || CONNECTOR_LABEL}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="pencil"
|
||||
aria-label={i18n.translate('xpack.serverlessSearch.connectors.editNameLabel', {
|
||||
defaultMessage: 'Edit connector name',
|
||||
})}
|
||||
onClick={() => setIsEditing(true)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
) : (
|
||||
<EuiForm>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormLabel>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.nameLabel', {
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
</EuiFormLabel>
|
||||
<EuiFieldText onChange={(event) => setNewName(event.target.value)} value={newName} />
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup direction="row" justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
type="submit"
|
||||
onClick={() => mutate(newName)}
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{SAVE_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiButton
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
setNewName(name);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
>
|
||||
{CANCEL_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiForm,
|
||||
EuiFormLabel,
|
||||
EuiIcon,
|
||||
EuiSuperSelect,
|
||||
} from '@elastic/eui';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
|
||||
|
||||
interface EditServiceTypeProps {
|
||||
connectorId: string;
|
||||
serviceType: string;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export const EditServiceType: React.FC<EditServiceTypeProps> = ({
|
||||
connectorId,
|
||||
serviceType,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { http } = useKibanaServices();
|
||||
const { data: connectorTypes } = useConnectorTypes();
|
||||
|
||||
const options =
|
||||
connectorTypes?.connectors.map((connectorType) => ({
|
||||
inputDisplay: (
|
||||
<EuiFlexGroup direction="row" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
title={connectorType.name}
|
||||
id={connectorType.serviceType}
|
||||
type={connectorType.iconPath}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{connectorType.name}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
value: connectorType.serviceType,
|
||||
})) || [];
|
||||
|
||||
const { isLoading, isSuccess, mutate } = useMutation({
|
||||
mutationFn: async (inputServiceType: string) => {
|
||||
const body = { service_type: inputServiceType };
|
||||
const result = await http.post(
|
||||
`/internal/serverless_search/connectors/${connectorId}/service_type`,
|
||||
{
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}, [isSuccess, onSuccess]);
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
<EuiFormLabel>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.serviceTypeLabel', {
|
||||
defaultMessage: 'Connector type',
|
||||
})}
|
||||
</EuiFormLabel>
|
||||
<EuiSuperSelect
|
||||
isLoading={isLoading}
|
||||
onChange={(event) => mutate(event)}
|
||||
options={options}
|
||||
valueOfSelected={serviceType ?? ''}
|
||||
/>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -17,18 +17,23 @@ import {
|
|||
EuiTitle,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { Connector, ConnectorServerSideDefinition } from '@kbn/search-connectors';
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { LEARN_MORE_LABEL } from '../../../common/i18n_string';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { useKibanaServices } from '../hooks/use_kibana';
|
||||
import { CREATE_CONNECTOR_PATH } from './connectors_router';
|
||||
import { useConnectorTypes } from '../hooks/api/use_connector_types';
|
||||
|
||||
export const ConnectorsOverview = () => {
|
||||
const { http } = useKibanaServices();
|
||||
const {
|
||||
application: { navigateToUrl },
|
||||
http,
|
||||
} = useKibanaServices();
|
||||
|
||||
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
|
||||
const connectorsPath = assetBasePath + '/connectors.svg';
|
||||
|
@ -39,19 +44,35 @@ export const ConnectorsOverview = () => {
|
|||
http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors'),
|
||||
});
|
||||
|
||||
const { data: connectorTypes } = useQuery({
|
||||
queryKey: ['fetchConnectorTypes'],
|
||||
queryFn: () =>
|
||||
http.fetch<{ connectors: ConnectorServerSideDefinition[] }>(
|
||||
'/internal/serverless_search/connector_types'
|
||||
),
|
||||
const { data: connectorTypes } = useConnectorTypes();
|
||||
|
||||
const {
|
||||
data: connector,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
mutate,
|
||||
} = useMutation({
|
||||
mutationFn: async () => {
|
||||
const result = await http.post<{ connector: Connector }>(
|
||||
'/internal/serverless_search/connectors'
|
||||
);
|
||||
return result.connector;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
navigateToUrl(`${CREATE_CONNECTOR_PATH}/${connector?.id}`);
|
||||
}, [connector, isSuccess, navigateToUrl]);
|
||||
|
||||
const createConnector = () => mutate();
|
||||
|
||||
return (
|
||||
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchConnectorsPage">
|
||||
<EuiPageTemplate.Header
|
||||
pageTitle={i18n.translate('xpack.serverlessSearch.connectors.title', {
|
||||
defaultMessage: 'Connectors',
|
||||
})}
|
||||
restrictWidth
|
||||
rightSideItems={[
|
||||
<EuiFlexGroup direction="row" alignItems="flexStart">
|
||||
<EuiFlexItem>
|
||||
|
@ -79,7 +100,12 @@ export const ConnectorsOverview = () => {
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton fill iconType="plusInCircleFilled">
|
||||
<EuiButton
|
||||
isLoading={isLoading}
|
||||
fill
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={() => createConnector()}
|
||||
>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.createConnector', {
|
||||
defaultMessage: 'Create connector',
|
||||
})}
|
||||
|
@ -107,7 +133,7 @@ export const ConnectorsOverview = () => {
|
|||
{(data?.connectors || []).length > 0 ? (
|
||||
<></>
|
||||
) : (
|
||||
<EuiPageTemplate.Section restrictWidth color="subdued">
|
||||
<EuiPageTemplate.Section grow restrictWidth color="subdued">
|
||||
<EuiFlexGroup alignItems="center" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="l" hasShadow={false} hasBorder>
|
||||
|
@ -248,7 +274,12 @@ export const ConnectorsOverview = () => {
|
|||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton fill iconType="plusInCircleFilled">
|
||||
<EuiButton
|
||||
isLoading={isLoading}
|
||||
fill
|
||||
iconType="plusInCircleFilled"
|
||||
onClick={() => createConnector()}
|
||||
>
|
||||
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', {
|
||||
defaultMessage: 'Create connector',
|
||||
})}
|
||||
|
@ -269,7 +300,7 @@ export const ConnectorsOverview = () => {
|
|||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{connectorTypes?.connectors.map((connectorType) => (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem key={connectorType.name}>
|
||||
<EuiToolTip content={connectorType.name}>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { Route, Routes } from '@kbn/shared-ux-router';
|
||||
import React from 'react';
|
||||
import { EditConnector } from './connectors/edit_connector';
|
||||
import { ConnectorsOverview } from './connectors_overview';
|
||||
|
||||
export const BASE_CONNECTORS_PATH = 'connectors';
|
||||
export const CREATE_CONNECTOR_SLUG = `create_connector`;
|
||||
export const CREATE_CONNECTOR_PATH = `${BASE_CONNECTORS_PATH}/${CREATE_CONNECTOR_SLUG}`;
|
||||
|
||||
export const ConnectorsRouter: React.FC = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={`/${CREATE_CONNECTOR_SLUG}/:id`}>
|
||||
<EditConnector />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<ConnectorsOverview />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
};
|
|
@ -16,6 +16,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { Router } from '@kbn/shared-ux-router';
|
||||
import { ServerlessSearchContext } from './hooks/use_kibana';
|
||||
|
||||
export async function renderApp(
|
||||
|
@ -23,7 +24,7 @@ export async function renderApp(
|
|||
core: CoreStart,
|
||||
services: ServerlessSearchContext
|
||||
) {
|
||||
const { ConnectorsOverview } = await import('./components/connectors_overview');
|
||||
const { ConnectorsRouter } = await import('./components/connectors_router');
|
||||
const queryClient = new QueryClient();
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme={core.theme}>
|
||||
|
@ -31,7 +32,9 @@ export async function renderApp(
|
|||
<QueryClientProvider client={queryClient}>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
<I18nProvider>
|
||||
<ConnectorsOverview />
|
||||
<Router history={services.history}>
|
||||
<ConnectorsRouter />
|
||||
</Router>
|
||||
</I18nProvider>
|
||||
</QueryClientProvider>
|
||||
</KibanaContextProvider>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { ConnectorServerSideDefinition } from '@kbn/search-connectors';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export const useConnectorTypes = () => {
|
||||
const { http } = useKibanaServices();
|
||||
return useQuery({
|
||||
queryKey: ['fetchConnectorTypes'],
|
||||
queryFn: () =>
|
||||
http.fetch<{ connectors: ConnectorServerSideDefinition[] }>(
|
||||
'/internal/serverless_search/connector_types'
|
||||
),
|
||||
});
|
||||
};
|
|
@ -6,13 +6,14 @@
|
|||
*/
|
||||
|
||||
import { CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public';
|
||||
import { AuthenticatedUser } from '@kbn/security-plugin/common';
|
||||
|
||||
export interface ServerlessSearchContext {
|
||||
cloud: CloudStart;
|
||||
history: AppMountParameters['history'];
|
||||
share: SharePluginStart;
|
||||
user?: AuthenticatedUser;
|
||||
}
|
||||
|
|
|
@ -10,3 +10,4 @@ export const MANAGEMENT_API_KEYS = '/app/management/security/api_keys';
|
|||
// Server Routes
|
||||
export const CREATE_API_KEY_PATH = '/internal/security/api_key';
|
||||
export const FETCH_INDICES_PATH = '/internal/serverless_search/indices';
|
||||
export const CREATE_CONNECTOR_PATH = '/internal/connectors';
|
||||
|
|
|
@ -45,7 +45,7 @@ export class ServerlessSearchPlugin
|
|||
euiIconType: 'logoElastic',
|
||||
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
|
||||
appRoute: '/app/elasticsearch',
|
||||
async mount({ element }: AppMountParameters) {
|
||||
async mount({ element, history }: AppMountParameters) {
|
||||
const { renderApp } = await import('./application/elasticsearch');
|
||||
const [coreStart, services] = await core.getStartServices();
|
||||
const { security } = services;
|
||||
|
@ -58,7 +58,7 @@ export class ServerlessSearchPlugin
|
|||
user = undefined;
|
||||
}
|
||||
|
||||
return await renderApp(element, coreStart, { user, ...services });
|
||||
return await renderApp(element, coreStart, { history, user, ...services });
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -71,12 +71,12 @@ export class ServerlessSearchPlugin
|
|||
euiIconType: 'logoElastic',
|
||||
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
|
||||
searchable: false,
|
||||
async mount({ element }: AppMountParameters) {
|
||||
async mount({ element, history }: AppMountParameters) {
|
||||
const { renderApp } = await import('./application/connectors');
|
||||
const [coreStart, services] = await core.getStartServices();
|
||||
|
||||
docLinks.setDocLinks(coreStart.docLinks.links);
|
||||
return await renderApp(element, coreStart, { ...services });
|
||||
return await renderApp(element, coreStart, { history, ...services });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CONNECTOR_DEFINITIONS, fetchConnectors } from '@kbn/search-connectors';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import {
|
||||
CONNECTOR_DEFINITIONS,
|
||||
createConnector,
|
||||
fetchConnectorById,
|
||||
fetchConnectors,
|
||||
updateConnectorNameAndDescription,
|
||||
updateConnectorServiceType,
|
||||
} from '@kbn/search-connectors';
|
||||
import { RouteDependencies } from '../plugin';
|
||||
|
||||
export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => {
|
||||
|
@ -27,6 +35,30 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
|
|||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/serverless_search/connector/{connectorId}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await fetchConnectorById(client.asCurrentUser, request.params.connectorId);
|
||||
|
||||
return result
|
||||
? response.ok({
|
||||
body: {
|
||||
connector: result.value,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
: response.notFound();
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/serverless_search/connector_types',
|
||||
|
@ -50,4 +82,118 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors',
|
||||
validate: {},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const connector = await createConnector(client.asCurrentUser, {
|
||||
indexName: null,
|
||||
instant_response: true,
|
||||
isNative: false,
|
||||
language: null,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
connector,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/name',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
name: schema.string(),
|
||||
}),
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await updateConnectorNameAndDescription(
|
||||
client.asCurrentUser,
|
||||
request.params.connectorId,
|
||||
{
|
||||
name: request.body.name,
|
||||
}
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
result,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/description',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
description: schema.string(),
|
||||
}),
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await updateConnectorNameAndDescription(
|
||||
client.asCurrentUser,
|
||||
request.params.connectorId,
|
||||
{
|
||||
description: request.body.description,
|
||||
}
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
result,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/service_type',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
service_type: schema.string(),
|
||||
}),
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await updateConnectorServiceType(
|
||||
client.asCurrentUser,
|
||||
request.params.connectorId,
|
||||
request.body.service_type
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
result,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -34,5 +34,6 @@
|
|||
"@kbn/core-lifecycle-browser",
|
||||
"@kbn/react-kibana-context-theme",
|
||||
"@kbn/search-connectors",
|
||||
"@kbn/shared-ux-router",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue