Skip to content

Commit

Permalink
[Connectors] Adapt Connectors UI for agentless (elastic#202179)
Browse files Browse the repository at this point in the history
## 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 <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored and CAWilson94 committed Dec 12, 2024
1 parent 78cc659 commit cb7e47d
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 100 deletions.
2 changes: 2 additions & 0 deletions packages/kbn-search-connectors/constants/connectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HttpLogic } from '../../../shared/http';
export interface GenerateConnectorNamesApiArgs {
connectorName?: string;
connectorType?: string;
isManagedConnector?: boolean;
}

export interface GenerateConnectorNamesApiResponse {
Expand All @@ -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 }),
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -65,66 +65,114 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) =>
createApiError,
attachApiError,
} = useValues(AttachIndexLogic);

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: connector.index_name,
label: removePrefixConnectorIndex(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);

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() ?? '')
name
.toLocaleLowerCase()
.includes(prefixConnectorIndex(query?.searchValue?.toLocaleLowerCase() || ''))
) ?? false;

const isFullMatch =
data?.indexNames.some(
(name) => name.toLocaleLowerCase() === query?.searchValue.toLocaleLowerCase()
(name) =>
name.toLocaleLowerCase() ===
prefixConnectorIndex(query?.searchValue?.toLocaleLowerCase() || '')
) ?? false;

const shouldPrependUserInputAsOption = !!query?.searchValue && hasMatchingOptions && !isFullMatch;
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 }];

Expand All @@ -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();
Expand All @@ -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 (
Expand All @@ -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 />
Expand All @@ -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}
>
Expand All @@ -217,40 +277,48 @@ 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 === ' ') {
event.preventDefault();
}
}}
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>
Expand All @@ -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', {
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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),
Expand All @@ -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 =
Expand Down Expand Up @@ -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>
Expand Down
Loading

0 comments on commit cb7e47d

Please sign in to comment.