[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';
export const MANAGED_CONNECTOR_INDEX_PREFIX = 'content-';
// needs to be a function because, docLinks are only populated with actual
// documentation links in browser after SearchConnectorsPlugin starts
export const getConnectorsDict = (): Record<string, ConnectorClientSideDefinition> => ({

View file

@ -10,6 +10,7 @@ import { HttpLogic } from '../../../shared/http';
export interface GenerateConnectorNamesApiArgs {
connectorName?: string;
connectorType?: string;
isManagedConnector?: boolean;
}
export interface GenerateConnectorNamesApiResponse {
@ -19,14 +20,16 @@ export interface GenerateConnectorNamesApiResponse {
}
export const generateConnectorNames = async (
{ connectorType, connectorName }: GenerateConnectorNamesApiArgs = { connectorType: 'custom' }
{ connectorType, connectorName, isManagedConnector }: GenerateConnectorNamesApiArgs = {
connectorType: 'custom',
}
) => {
if (connectorType === '') {
connectorType = 'custom';
}
const route = `/internal/enterprise_search/connectors/generate_connector_name`;
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 { 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';
@ -65,66 +65,114 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
createApiError,
attachApiError,
} = 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 { data, status } = useValues(FetchAvailableIndicesAPILogic);
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 = () => {
if (selectedIndex?.shouldCreate) {
createIndex({ indexName: selectedIndex.label, language: selectedLanguage ?? null });
} else if (selectedIndex && !(selectedIndex.label === connector.index_name)) {
attachIndex({ connectorId: connector.id, indexName: selectedIndex.label });
if (!selectedIndex) return;
// Always attach and/or create prefixed index for managed connectors
const prefixedIndex = prefixConnectorIndex(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
? []
: data?.indexNames.map((name) => {
return {
: data?.indexNames
.filter((name) => !connector.is_native || name.startsWith(MANAGED_CONNECTOR_INDEX_PREFIX))
.map((name) => ({
label: name,
};
}) ?? [];
value: removePrefixConnectorIndex(name),
})) ?? [];
const hasMatchingOptions =
data?.indexNames.some((name) =>
name.toLocaleLowerCase().includes(query?.searchValue.toLocaleLowerCase() ?? '')
) ?? false;
const isFullMatch =
data?.indexNames.some(
(name) => name.toLocaleLowerCase() === query?.searchValue.toLocaleLowerCase()
name
.toLocaleLowerCase()
.includes(prefixConnectorIndex(query?.searchValue?.toLocaleLowerCase() || ''))
) ?? 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
? [
...[
{
label: CREATE_NEW_INDEX_GROUP_LABEL,
options: [
{
label: query.searchValue,
},
],
},
],
...[{ label: SELECT_EXISTING_INDEX_GROUP_LABEL, options }],
{
label: CREATE_NEW_INDEX_GROUP_LABEL,
options: [
{
label: prefixConnectorIndex(query!.searchValue),
value: query!.searchValue,
},
],
},
{ 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]);
useEffect(() => {
setSanitizedName(formatApiName(connector.name));
// Suggested name for managed connector should include the content- prefix
setSanitizedName(prefixConnectorIndex(formatApiName(connector.name)));
}, [connector.name]);
const { hash } = useLocation();
@ -170,9 +219,10 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
}
)
: attachApiError?.body?.message || createApiError?.body?.message || undefined;
if (indexName) {
// We don't want to let people edit indices when on the index route
return <></>;
// Do not render when on the index route
return null;
}
return (
@ -189,8 +239,8 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
<FormattedMessage
id="xpack.enterpriseSearch.attachIndexBox.thisIndexWillHoldTextLabel"
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
language analyzer for the index."
for relevant search experiences. Give your index a unique name and optionally set a default
language analyzer for the index."
/>
</EuiText>
<EuiSpacer />
@ -201,10 +251,20 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexLabel',
{ defaultMessage: 'Associated index' }
)}
helpText={i18n.translate(
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexHelpTextLabel',
{ defaultMessage: 'You can use an existing index or create a new one.' }
)}
helpText={
connector.is_native
? 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}
isInvalid={!!error}
>
@ -217,11 +277,13 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
'xpack.enterpriseSearch.attachIndexBox.euiFormRow.indexSelector.customOption',
{
defaultMessage: 'Create index {searchValue}',
values: { searchValue: '{searchValue}' },
values: { searchValue: prefixConnectorIndex('{searchValue}') },
}
)}
isLoading={isLoading}
options={groupedOptions}
singleSelection={{ asPlainText: connector.is_native }}
prepend={connector.is_native ? MANAGED_CONNECTOR_INDEX_PREFIX : undefined}
onKeyDown={(event) => {
// Index name should not contain spaces
if (event.key === ' ') {
@ -229,28 +291,34 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
}
}}
onSearchChange={(searchValue) => {
// Match by option value to ensure accurate comparison with non-prefixed
// user input for managed connectors
setQuery({
isFullMatch: options.some((option) => option.label === searchValue),
searchValue,
isFullMatch: options.some(
(option) => option.value === prefixConnectorIndex(searchValue)
),
searchValue: prefixConnectorIndex(searchValue),
});
}}
onChange={(selection) => {
const currentSelection = selection[0] ?? undefined;
const currentSelection = selection[0];
const selectedIndexOption = currentSelection
? {
label: currentSelection.label,
label: removePrefixConnectorIndex(currentSelection.label),
shouldCreate:
shouldPrependUserInputAsOption &&
!!(currentSelection?.label === query?.searchValue),
currentSelection.value === query?.searchValue,
}
: undefined;
setSelectedIndex(selectedIndexOption);
}}
selectedOptions={selectedIndex ? [selectedIndex] : undefined}
onCreateOption={(value) => {
setSelectedIndex({ label: value.trim(), shouldCreate: true });
setSelectedIndex({
label: removePrefixConnectorIndex(value.trim()),
shouldCreate: true,
});
}}
singleSelection
/>
</EuiFormRow>
</EuiFlexItem>
@ -261,8 +329,12 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
<EuiButton
data-test-subj="entSearchContent-connector-connectorDetail-saveConfigurationButton"
data-telemetry-id="entSearchContent-connector-connectorDetail-saveConfigurationButton"
onClick={() => onSave()}
disabled={!selectedIndex || selectedIndex.label === connector.index_name}
onClick={onSave}
disabled={
!selectedIndex ||
prefixConnectorIndex(selectedIndex.label) === connector.index_name ||
!!error
}
isLoading={isSaveLoading}
>
{i18n.translate('xpack.enterpriseSearch.attachIndexBox.saveConfigurationButtonLabel', {
@ -314,15 +386,13 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
}
)}
</EuiButton>
{indexExists[sanitizedName] ? (
{indexExists[sanitizedName] && (
<EuiText size="xs">
{i18n.translate('xpack.enterpriseSearch.attachIndexBox.indexNameExistsError', {
defaultMessage: 'Index with name {indexName} already exists',
values: { indexName: sanitizedName },
})}
</EuiText>
) : (
<></>
)}
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -26,9 +26,6 @@ import { BetaConnectorCallout } from '../../../shared/beta/beta_connector_callou
import { HttpLogic } from '../../../shared/http';
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 { NativeConnectorConfigurationConfig } from '../search_index/connector/native_connector_configuration/native_connector_configuration_config';
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 { config, connectorTypes: connectors } = useValues(KibanaLogic);
const { errorConnectingMessage } = useValues(HttpLogic);
const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic);
const NATIVE_CONNECTORS = useMemo(
() => connectors.filter(({ isNative }) => isNative),
@ -68,7 +64,6 @@ export const NativeConnectorConfiguration: React.FC = () => {
};
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
const isBeta =
@ -170,23 +165,6 @@ export const NativeConnectorConfiguration: React.FC = () => {
<EuiSpacer />
</EuiPanel>
<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>
<ConvertConnector />
</EuiPanel>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -842,17 +842,19 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
body: schema.object({
connectorName: schema.maybe(schema.string()),
connectorType: schema.string(),
isManagedConnector: schema.maybe(schema.boolean()),
}),
},
},
elasticsearchErrorHandler(log, async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const { connectorType, connectorName } = request.body;
const { connectorType, connectorName, isManagedConnector } = request.body;
try {
const generatedNames = await generateConnectorName(
client,
connectorType ?? 'custom',
connectorName
connectorName,
isManagedConnector
);
return response.ok({
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.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.nativeConfigurationConnector.apiKey.title": "Clé d'API",
"xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title": "Configuration",
"xpack.enterpriseSearch.content.connectors.breadcrumb": "Connecteurs",
"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.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.connectors.breadcrumb": "コネクター",
"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.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.connectors.breadcrumb": "连接器",
"xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "配置",