[Connectors] Adapt Connectors UI for agentless (#202179)

## Summary

Couple of changes to support Elastic-managed connectors in 9.x

### Video overview


https://github.com/user-attachments/assets/086ae96a-0520-483e-b055-5e672b3f65f5

### List of changes

1. Elatic-managed connectors now enforce `content-` prefix

<img width="400" alt="Screenshot 2024-11-28 at 14 55 27"
src="https://github.com/user-attachments/assets/aa7f6d6d-39af-42ad-b5f1-5455e37006df">


2. Banner about not attached index changed to warning (yellow) instead
of danger (red) - as this is not an error state
<img width="400" alt="Screenshot 2024-11-28 at 14 54 48"
src="https://github.com/user-attachments/assets/00d5c332-0366-4420-8934-32404b4fb1c3">

3. Get rid of native connector API keys from UI - as they won't work
anyway without ent-search node

<img width="400" alt="Screenshot 2024-11-28 at 14 56 22"
src="https://github.com/user-attachments/assets/6fcea6b4-0559-4419-83d6-4a9db9a71c88">


4. Index name generation for native connectors, `content-` is always
added as prefix for native


<img width="400" alt="Screenshot 2024-11-28 at 14 57 01"
src="https://github.com/user-attachments/assets/c17ccc35-1d91-4666-8db5-b796685c928e">



### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jedr Blaszyk 2024-11-29 13:54:49 +01:00 committed by GitHub
parent fd589b844c
commit 89063df988
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 180 additions and 107 deletions

View file

@ -15,6 +15,8 @@ import {
import { docLinks } from './doc_links'; import { docLinks } from './doc_links';
export const MANAGED_CONNECTOR_INDEX_PREFIX = 'content-';
// needs to be a function because, docLinks are only populated with actual // needs to be a function because, docLinks are only populated with actual
// documentation links in browser after SearchConnectorsPlugin starts // documentation links in browser after SearchConnectorsPlugin starts
export const getConnectorsDict = (): Record<string, ConnectorClientSideDefinition> => ({ export const getConnectorsDict = (): Record<string, ConnectorClientSideDefinition> => ({

View file

@ -10,6 +10,7 @@ import { HttpLogic } from '../../../shared/http';
export interface GenerateConnectorNamesApiArgs { export interface GenerateConnectorNamesApiArgs {
connectorName?: string; connectorName?: string;
connectorType?: string; connectorType?: string;
isManagedConnector?: boolean;
} }
export interface GenerateConnectorNamesApiResponse { export interface GenerateConnectorNamesApiResponse {
@ -19,14 +20,16 @@ export interface GenerateConnectorNamesApiResponse {
} }
export const generateConnectorNames = async ( export const generateConnectorNames = async (
{ connectorType, connectorName }: GenerateConnectorNamesApiArgs = { connectorType: 'custom' } { connectorType, connectorName, isManagedConnector }: GenerateConnectorNamesApiArgs = {
connectorType: 'custom',
}
) => { ) => {
if (connectorType === '') { if (connectorType === '') {
connectorType = 'custom'; connectorType = 'custom';
} }
const route = `/internal/enterprise_search/connectors/generate_connector_name`; const route = `/internal/enterprise_search/connectors/generate_connector_name`;
return await HttpLogic.values.http.post(route, { return await HttpLogic.values.http.post(route, {
body: JSON.stringify({ connectorName, connectorType }), body: JSON.stringify({ connectorName, connectorType, isManagedConnector }),
}); });
}; };

View file

@ -27,7 +27,7 @@ import {
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { Connector } from '@kbn/search-connectors'; import { Connector, MANAGED_CONNECTOR_INDEX_PREFIX } from '@kbn/search-connectors';
import { Status } from '../../../../../common/types/api'; import { Status } from '../../../../../common/types/api';
@ -65,66 +65,114 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
createApiError, createApiError,
attachApiError, attachApiError,
} = useValues(AttachIndexLogic); } = useValues(AttachIndexLogic);
const [selectedIndex, setSelectedIndex] = useState<
{ label: string; shouldCreate?: boolean } | undefined
>(
connector.index_name
? {
label: connector.index_name,
}
: undefined
);
const [selectedLanguage] = useState<string>();
const [query, setQuery] = useState<{
isFullMatch: boolean;
searchValue: string;
}>();
const [sanitizedName, setSanitizedName] = useState<string>(formatApiName(connector.name));
const { makeRequest } = useActions(FetchAvailableIndicesAPILogic); const { makeRequest } = useActions(FetchAvailableIndicesAPILogic);
const { data, status } = useValues(FetchAvailableIndicesAPILogic); const { data, status } = useValues(FetchAvailableIndicesAPILogic);
const isLoading = [Status.IDLE, Status.LOADING].includes(status); const isLoading = [Status.IDLE, Status.LOADING].includes(status);
// Helper function to remove the managed connector index prefix from the index name
const removePrefixConnectorIndex = (connectorIndexName: string) => {
if (!connector.is_native) {
return connectorIndexName;
}
if (connectorIndexName.startsWith(MANAGED_CONNECTOR_INDEX_PREFIX)) {
return connectorIndexName.substring(MANAGED_CONNECTOR_INDEX_PREFIX.length);
}
return connectorIndexName;
};
// Helper function to add the managed connector index prefix to the index name
const prefixConnectorIndex = (connectorIndexName: string) => {
if (!connector.is_native) {
return connectorIndexName;
}
if (connectorIndexName.startsWith(MANAGED_CONNECTOR_INDEX_PREFIX)) {
return connectorIndexName;
}
return `${MANAGED_CONNECTOR_INDEX_PREFIX}${connectorIndexName}`;
};
const [query, setQuery] = useState<{
isFullMatch: boolean;
searchValue: string;
}>();
const [sanitizedName, setSanitizedName] = useState<string>(
prefixConnectorIndex(formatApiName(connector.name))
);
const [selectedIndex, setSelectedIndex] = useState<
{ label: string; shouldCreate?: boolean } | undefined
>(
// For managed connectors, the index name should be displayed without prefix
// As `content-` is fixed UI element
connector.index_name
? {
label: removePrefixConnectorIndex(connector.index_name),
}
: undefined
);
const onSave = () => { const onSave = () => {
if (selectedIndex?.shouldCreate) { if (!selectedIndex) return;
createIndex({ indexName: selectedIndex.label, language: selectedLanguage ?? null }); // Always attach and/or create prefixed index for managed connectors
} else if (selectedIndex && !(selectedIndex.label === connector.index_name)) { const prefixedIndex = prefixConnectorIndex(selectedIndex.label);
attachIndex({ connectorId: connector.id, indexName: selectedIndex.label }); if (selectedIndex.shouldCreate) {
createIndex({
indexName: prefixedIndex,
language: null,
});
} else if (connector.index_name !== prefixedIndex) {
attachIndex({
connectorId: connector.id,
indexName: prefixedIndex,
});
} }
}; };
// For managed connectors ensure that only prefixed indices are displayed in the dropdown
// This takes care of the initial component state where all indices could be displayed briefly
const options: Array<EuiComboBoxOptionOption<string>> = isLoading const options: Array<EuiComboBoxOptionOption<string>> = isLoading
? [] ? []
: data?.indexNames.map((name) => { : data?.indexNames
return { .filter((name) => !connector.is_native || name.startsWith(MANAGED_CONNECTOR_INDEX_PREFIX))
.map((name) => ({
label: name, label: name,
}; value: removePrefixConnectorIndex(name),
}) ?? []; })) ?? [];
const hasMatchingOptions = const hasMatchingOptions =
data?.indexNames.some((name) => data?.indexNames.some((name) =>
name.toLocaleLowerCase().includes(query?.searchValue.toLocaleLowerCase() ?? '') name
) ?? false; .toLocaleLowerCase()
const isFullMatch = .includes(prefixConnectorIndex(query?.searchValue?.toLocaleLowerCase() || ''))
data?.indexNames.some(
(name) => name.toLocaleLowerCase() === query?.searchValue.toLocaleLowerCase()
) ?? false; ) ?? false;
const shouldPrependUserInputAsOption = !!query?.searchValue && hasMatchingOptions && !isFullMatch; const isFullMatch =
data?.indexNames.some(
(name) =>
name.toLocaleLowerCase() ===
prefixConnectorIndex(query?.searchValue?.toLocaleLowerCase() || '')
) ?? false;
const shouldPrependUserInputAsOption =
!!query &&
!!query.searchValue &&
query.searchValue !== MANAGED_CONNECTOR_INDEX_PREFIX &&
hasMatchingOptions &&
!isFullMatch;
const groupedOptions: Array<EuiComboBoxOptionOption<string>> = shouldPrependUserInputAsOption const groupedOptions: Array<EuiComboBoxOptionOption<string>> = shouldPrependUserInputAsOption
? [ ? [
...[ {
{ label: CREATE_NEW_INDEX_GROUP_LABEL,
label: CREATE_NEW_INDEX_GROUP_LABEL, options: [
options: [ {
{ label: prefixConnectorIndex(query!.searchValue),
label: query.searchValue, value: query!.searchValue,
}, },
], ],
}, },
], { label: SELECT_EXISTING_INDEX_GROUP_LABEL, options },
...[{ label: SELECT_EXISTING_INDEX_GROUP_LABEL, options }],
] ]
: [{ label: SELECT_EXISTING_INDEX_GROUP_LABEL, options }]; : [{ label: SELECT_EXISTING_INDEX_GROUP_LABEL, options }];
@ -144,7 +192,8 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
}, [query]); }, [query]);
useEffect(() => { useEffect(() => {
setSanitizedName(formatApiName(connector.name)); // Suggested name for managed connector should include the content- prefix
setSanitizedName(prefixConnectorIndex(formatApiName(connector.name)));
}, [connector.name]); }, [connector.name]);
const { hash } = useLocation(); const { hash } = useLocation();
@ -170,9 +219,10 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
} }
) )
: attachApiError?.body?.message || createApiError?.body?.message || undefined; : attachApiError?.body?.message || createApiError?.body?.message || undefined;
if (indexName) { if (indexName) {
// We don't want to let people edit indices when on the index route // Do not render when on the index route
return <></>; return null;
} }
return ( return (
@ -189,8 +239,8 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
<FormattedMessage <FormattedMessage
id="xpack.enterpriseSearch.attachIndexBox.thisIndexWillHoldTextLabel" id="xpack.enterpriseSearch.attachIndexBox.thisIndexWillHoldTextLabel"
defaultMessage="This index will hold your data source content, and is optimized with default field mappings defaultMessage="This index will hold your data source content, and is optimized with default field mappings
for relevant search experiences. Give your index a unique name and optionally set a default for relevant search experiences. Give your index a unique name and optionally set a default
language analyzer for the index." language analyzer for the index."
/> />
</EuiText> </EuiText>
<EuiSpacer /> <EuiSpacer />
@ -201,10 +251,20 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexLabel', 'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexLabel',
{ defaultMessage: 'Associated index' } { defaultMessage: 'Associated index' }
)} )}
helpText={i18n.translate( helpText={
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexHelpTextLabel', connector.is_native
{ defaultMessage: 'You can use an existing index or create a new one.' } ? i18n.translate(
)} 'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedManagedConnectorIndexHelpTextLabel',
{
defaultMessage:
'Managed connector indices must be prefixed. Use an existing index or create a new one.',
}
)
: i18n.translate(
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexHelpTextLabel',
{ defaultMessage: 'You can use an existing index or create a new one.' }
)
}
error={error} error={error}
isInvalid={!!error} isInvalid={!!error}
> >
@ -217,11 +277,13 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.indexSelector.customOption', 'xpack.enterpriseSearch.attachIndexBox.euiFormRow.indexSelector.customOption',
{ {
defaultMessage: 'Create index {searchValue}', defaultMessage: 'Create index {searchValue}',
values: { searchValue: '{searchValue}' }, values: { searchValue: prefixConnectorIndex('{searchValue}') },
} }
)} )}
isLoading={isLoading} isLoading={isLoading}
options={groupedOptions} options={groupedOptions}
singleSelection={{ asPlainText: connector.is_native }}
prepend={connector.is_native ? MANAGED_CONNECTOR_INDEX_PREFIX : undefined}
onKeyDown={(event) => { onKeyDown={(event) => {
// Index name should not contain spaces // Index name should not contain spaces
if (event.key === ' ') { if (event.key === ' ') {
@ -229,28 +291,34 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
} }
}} }}
onSearchChange={(searchValue) => { onSearchChange={(searchValue) => {
// Match by option value to ensure accurate comparison with non-prefixed
// user input for managed connectors
setQuery({ setQuery({
isFullMatch: options.some((option) => option.label === searchValue), isFullMatch: options.some(
searchValue, (option) => option.value === prefixConnectorIndex(searchValue)
),
searchValue: prefixConnectorIndex(searchValue),
}); });
}} }}
onChange={(selection) => { onChange={(selection) => {
const currentSelection = selection[0] ?? undefined; const currentSelection = selection[0];
const selectedIndexOption = currentSelection const selectedIndexOption = currentSelection
? { ? {
label: currentSelection.label, label: removePrefixConnectorIndex(currentSelection.label),
shouldCreate: shouldCreate:
shouldPrependUserInputAsOption && shouldPrependUserInputAsOption &&
!!(currentSelection?.label === query?.searchValue), currentSelection.value === query?.searchValue,
} }
: undefined; : undefined;
setSelectedIndex(selectedIndexOption); setSelectedIndex(selectedIndexOption);
}} }}
selectedOptions={selectedIndex ? [selectedIndex] : undefined} selectedOptions={selectedIndex ? [selectedIndex] : undefined}
onCreateOption={(value) => { onCreateOption={(value) => {
setSelectedIndex({ label: value.trim(), shouldCreate: true }); setSelectedIndex({
label: removePrefixConnectorIndex(value.trim()),
shouldCreate: true,
});
}} }}
singleSelection
/> />
</EuiFormRow> </EuiFormRow>
</EuiFlexItem> </EuiFlexItem>
@ -261,8 +329,12 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
<EuiButton <EuiButton
data-test-subj="entSearchContent-connector-connectorDetail-saveConfigurationButton" data-test-subj="entSearchContent-connector-connectorDetail-saveConfigurationButton"
data-telemetry-id="entSearchContent-connector-connectorDetail-saveConfigurationButton" data-telemetry-id="entSearchContent-connector-connectorDetail-saveConfigurationButton"
onClick={() => onSave()} onClick={onSave}
disabled={!selectedIndex || selectedIndex.label === connector.index_name} disabled={
!selectedIndex ||
prefixConnectorIndex(selectedIndex.label) === connector.index_name ||
!!error
}
isLoading={isSaveLoading} isLoading={isSaveLoading}
> >
{i18n.translate('xpack.enterpriseSearch.attachIndexBox.saveConfigurationButtonLabel', { {i18n.translate('xpack.enterpriseSearch.attachIndexBox.saveConfigurationButtonLabel', {
@ -314,15 +386,13 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
} }
)} )}
</EuiButton> </EuiButton>
{indexExists[sanitizedName] ? ( {indexExists[sanitizedName] && (
<EuiText size="xs"> <EuiText size="xs">
{i18n.translate('xpack.enterpriseSearch.attachIndexBox.indexNameExistsError', { {i18n.translate('xpack.enterpriseSearch.attachIndexBox.indexNameExistsError', {
defaultMessage: 'Index with name {indexName} already exists', defaultMessage: 'Index with name {indexName} already exists',
values: { indexName: sanitizedName }, values: { indexName: sanitizedName },
})} })}
</EuiText> </EuiText>
) : (
<></>
)} )}
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>

View file

@ -26,9 +26,6 @@ import { BetaConnectorCallout } from '../../../shared/beta/beta_connector_callou
import { HttpLogic } from '../../../shared/http'; import { HttpLogic } from '../../../shared/http';
import { KibanaLogic } from '../../../shared/kibana'; import { KibanaLogic } from '../../../shared/kibana';
import { GenerateConnectorApiKeyApiLogic } from '../../api/connector/generate_connector_api_key_api_logic';
import { ApiKeyConfig } from '../search_index/connector/api_key_configuration';
import { ConvertConnector } from '../search_index/connector/native_connector_configuration/convert_connector'; import { ConvertConnector } from '../search_index/connector/native_connector_configuration/convert_connector';
import { NativeConnectorConfigurationConfig } from '../search_index/connector/native_connector_configuration/native_connector_configuration_config'; import { NativeConnectorConfigurationConfig } from '../search_index/connector/native_connector_configuration/native_connector_configuration_config';
import { ResearchConfiguration } from '../search_index/connector/native_connector_configuration/research_configuration'; import { ResearchConfiguration } from '../search_index/connector/native_connector_configuration/research_configuration';
@ -41,7 +38,6 @@ export const NativeConnectorConfiguration: React.FC = () => {
const { connector } = useValues(ConnectorViewLogic); const { connector } = useValues(ConnectorViewLogic);
const { config, connectorTypes: connectors } = useValues(KibanaLogic); const { config, connectorTypes: connectors } = useValues(KibanaLogic);
const { errorConnectingMessage } = useValues(HttpLogic); const { errorConnectingMessage } = useValues(HttpLogic);
const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic);
const NATIVE_CONNECTORS = useMemo( const NATIVE_CONNECTORS = useMemo(
() => connectors.filter(({ isNative }) => isNative), () => connectors.filter(({ isNative }) => isNative),
@ -68,7 +64,6 @@ export const NativeConnectorConfiguration: React.FC = () => {
}; };
const iconPath = nativeConnector.iconPath; const iconPath = nativeConnector.iconPath;
const hasApiKey = !!(connector.api_key_id ?? apiKeyData);
// TODO service_type === "" is considered unknown/custom connector multipleplaces replace all of them with a better solution // TODO service_type === "" is considered unknown/custom connector multipleplaces replace all of them with a better solution
const isBeta = const isBeta =
@ -170,23 +165,6 @@ export const NativeConnectorConfiguration: React.FC = () => {
<EuiSpacer /> <EuiSpacer />
</EuiPanel> </EuiPanel>
<EuiSpacer /> <EuiSpacer />
<EuiPanel hasBorder>
<EuiTitle size="s">
<h4>
{i18n.translate(
'xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.apiKey.title',
{ defaultMessage: 'API Key' }
)}
</h4>
</EuiTitle>
<EuiSpacer size="m" />
<ApiKeyConfig
indexName={connector.index_name || ''}
hasApiKey={hasApiKey}
isNative
/>
</EuiPanel>
<EuiSpacer />
<EuiPanel hasBorder> <EuiPanel hasBorder>
<ConvertConnector /> <ConvertConnector />
</EuiPanel> </EuiPanel>

View file

@ -95,7 +95,7 @@ export const ConnectorDetailOverview: React.FC = () => {
<> <>
<EuiCallOut <EuiCallOut
iconType="iInCircle" iconType="iInCircle"
color="danger" color="warning"
title={i18n.translate( title={i18n.translate(
'xpack.enterpriseSearch.content.connectors.overview.connectorNoIndexCallOut.title', 'xpack.enterpriseSearch.content.connectors.overview.connectorNoIndexCallOut.title',
{ {
@ -115,7 +115,7 @@ export const ConnectorDetailOverview: React.FC = () => {
</EuiText> </EuiText>
<EuiSpacer /> <EuiSpacer />
<EuiButtonTo <EuiButtonTo
color="danger" color="warning"
fill fill
to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId: connector.id, connectorId: connector.id,

View file

@ -144,6 +144,7 @@ export const StartStep: React.FC<StartStepProps> = ({
generateConnectorName({ generateConnectorName({
connectorName: rawName, connectorName: rawName,
connectorType: selectedConnector.serviceType, connectorType: selectedConnector.serviceType,
isManagedConnector: selectedConnector.isNative,
}); });
} }
}} }}

View file

@ -192,6 +192,7 @@ export const NewConnectorLogic = kea<MakeLogicType<NewConnectorValues, NewConnec
if (connector) { if (connector) {
actions.generateConnectorName({ actions.generateConnectorName({
connectorType: connector.serviceType, connectorType: connector.serviceType,
isManagedConnector: connector.isNative,
}); });
} }
}, },

View file

@ -7,7 +7,11 @@
import { IScopedClusterClient } from '@kbn/core/server'; import { IScopedClusterClient } from '@kbn/core/server';
import { Connector, CONNECTORS_INDEX } from '@kbn/search-connectors'; import {
Connector,
CONNECTORS_INDEX,
MANAGED_CONNECTOR_INDEX_PREFIX,
} from '@kbn/search-connectors';
import { createIndex } from '../indices/create_index'; import { createIndex } from '../indices/create_index';
import { indexOrAliasExists } from '../indices/exists_index'; import { indexOrAliasExists } from '../indices/exists_index';
@ -20,10 +24,10 @@ export const generateConfig = async (client: IScopedClusterClient, connector: Co
if (connector.index_name) { if (connector.index_name) {
associatedIndex = connector.index_name; associatedIndex = connector.index_name;
} else { } else {
associatedIndex = await generatedIndexName( const indexPrefix = connector.is_native ? MANAGED_CONNECTOR_INDEX_PREFIX : ''; // managed connectors need to be prefixed with `content-`
client, const connectorReference = connector.name || connector.service_type || 'my-connector'; // pass a default name to generate a readable index name rather than gibberish
connector.name || connector.service_type || 'my-connector' // pass a default name to generate a readable index name rather than gibberish
); associatedIndex = await generatedIndexName(client, indexPrefix + connectorReference);
} }
if (!indexOrAliasExists(client, associatedIndex)) { if (!indexOrAliasExists(client, associatedIndex)) {

View file

@ -9,22 +9,34 @@ import { v4 as uuidv4 } from 'uuid';
import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
import { MANAGED_CONNECTOR_INDEX_PREFIX } from '@kbn/search-connectors';
import { ErrorCode } from '../../../common/types/error_codes'; import { ErrorCode } from '../../../common/types/error_codes';
import { toAlphanumeric } from '../../../common/utils/to_alphanumeric'; import { toAlphanumeric } from '../../../common/utils/to_alphanumeric';
import { indexOrAliasExists } from '../indices/exists_index'; import { indexOrAliasExists } from '../indices/exists_index';
const addIndexPrefix = (indexName: string, isManagedConnector: boolean): string => {
const indexPrefix = isManagedConnector ? MANAGED_CONNECTOR_INDEX_PREFIX : 'connector-';
return `${indexPrefix}${indexName}`;
};
const addConnectorPrefix = (indexName: string): string => {
return `connector-${indexName}`;
};
export const generateConnectorName = async ( export const generateConnectorName = async (
client: IScopedClusterClient, client: IScopedClusterClient,
connectorType: string, connectorType: string,
userConnectorName?: string userConnectorName?: string,
isManagedConnector: boolean = false
): Promise<{ apiKeyName: string; connectorName: string; indexName: string }> => { ): Promise<{ apiKeyName: string; connectorName: string; indexName: string }> => {
const prefix = toAlphanumeric(connectorType); const prefix = toAlphanumeric(connectorType);
if (!prefix || prefix.length === 0) { if (!prefix || prefix.length === 0) {
throw new Error('Connector type or connectorName is required'); throw new Error('Connector type or connectorName is required');
} }
if (userConnectorName) { if (userConnectorName) {
let indexName = `connector-${userConnectorName}`; let indexName = addIndexPrefix(userConnectorName, isManagedConnector);
const resultSameName = await indexOrAliasExists(client, indexName); const resultSameName = await indexOrAliasExists(client, indexName);
// index with same name doesn't exist // index with same name doesn't exist
if (!resultSameName) { if (!resultSameName) {
@ -36,12 +48,14 @@ export const generateConnectorName = async (
} }
// if the index name already exists, we will generate until it doesn't for 20 times // if the index name already exists, we will generate until it doesn't for 20 times
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
indexName = `connector-${userConnectorName}-${uuidv4().split('-')[1].slice(0, 4)}`; const randomizedConnectorName = `${userConnectorName}-${uuidv4().split('-')[1].slice(0, 4)}`;
indexName = addIndexPrefix(randomizedConnectorName, isManagedConnector);
const result = await indexOrAliasExists(client, indexName); const result = await indexOrAliasExists(client, indexName);
if (!result) { if (!result) {
return { return {
apiKeyName: indexName, apiKeyName: addConnectorPrefix(randomizedConnectorName),
connectorName: userConnectorName, connectorName: userConnectorName,
indexName, indexName,
}; };
@ -49,14 +63,15 @@ export const generateConnectorName = async (
} }
} else { } else {
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
const connectorName = `${prefix}-${uuidv4().split('-')[1].slice(0, 4)}`; const randomizedConnectorName = `${prefix}-${uuidv4().split('-')[1].slice(0, 4)}`;
const indexName = `connector-${connectorName}`; const indexName = addIndexPrefix(randomizedConnectorName, isManagedConnector);
const result = await indexOrAliasExists(client, indexName); const result = await indexOrAliasExists(client, indexName);
if (!result) { if (!result) {
return { return {
apiKeyName: indexName, apiKeyName: addConnectorPrefix(randomizedConnectorName),
connectorName, connectorName: randomizedConnectorName,
indexName, indexName,
}; };
} }

View file

@ -842,17 +842,19 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
body: schema.object({ body: schema.object({
connectorName: schema.maybe(schema.string()), connectorName: schema.maybe(schema.string()),
connectorType: schema.string(), connectorType: schema.string(),
isManagedConnector: schema.maybe(schema.boolean()),
}), }),
}, },
}, },
elasticsearchErrorHandler(log, async (context, request, response) => { elasticsearchErrorHandler(log, async (context, request, response) => {
const { client } = (await context.core).elasticsearch; const { client } = (await context.core).elasticsearch;
const { connectorType, connectorName } = request.body; const { connectorType, connectorName, isManagedConnector } = request.body;
try { try {
const generatedNames = await generateConnectorName( const generatedNames = await generateConnectorName(
client, client,
connectorType ?? 'custom', connectorType ?? 'custom',
connectorName connectorName,
isManagedConnector
); );
return response.ok({ return response.ok({
body: generatedNames, body: generatedNames,

View file

@ -17436,7 +17436,6 @@
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label": "Terminer le déploiement plus tard", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label": "Terminer le déploiement plus tard",
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title": "En attente de votre connecteur", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title": "En attente de votre connecteur",
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title": "En attente du contrôle de votre connecteur", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title": "En attente du contrôle de votre connecteur",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.apiKey.title": "Clé d'API",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "Configuration", "xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "Configuration",
"xpack.enterpriseSearch.content.connectors.breadcrumb": "Connecteurs", "xpack.enterpriseSearch.content.connectors.breadcrumb": "Connecteurs",
"xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "Configuration", "xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "Configuration",

View file

@ -17411,7 +17411,6 @@
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label": "後でデプロイを完了する", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label": "後でデプロイを完了する",
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title": "コネクターを待機しています", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title": "コネクターを待機しています",
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title": "コネクターのチェックインを待機しています", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title": "コネクターのチェックインを待機しています",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.apiKey.title": "API キー",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "構成", "xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "構成",
"xpack.enterpriseSearch.content.connectors.breadcrumb": "コネクター", "xpack.enterpriseSearch.content.connectors.breadcrumb": "コネクター",
"xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "構成", "xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "構成",

View file

@ -17079,7 +17079,6 @@
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label": "稍后完成部署", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label": "稍后完成部署",
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title": "等候您的连接器", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title": "等候您的连接器",
"xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title": "等待您的连接器签入", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title": "等待您的连接器签入",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.apiKey.title": "API 密钥",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "配置", "xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "配置",
"xpack.enterpriseSearch.content.connectors.breadcrumb": "连接器", "xpack.enterpriseSearch.content.connectors.breadcrumb": "连接器",
"xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "配置", "xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "配置",