Skip to content

Commit 23c28eb

Browse files
[Connectors] Detect deploying agentless infra state (#205395)
## Summary Let’s start with simple logic to detect the state where we’re waiting for the agentless infrastructure to be provisioned. In a recent change (elastic/connectors#3014), connectors now send a heartbeat immediately upon the framework’s startup. Therefore, we can use the “last seen” timestamp (populated on heartbeat) to detect when the infrastructure has started. If we find this logic insufficient to cover all cases, we can adapt the ConnectorViewLogic to also call Fleet APIs for more accurate missing/ready agentless host detection . Let’s keep it simple for now and iterate as needed. ### Changes - Add logic in ConnectorViewLogic to compute the isWaitingOnAgentlessDeployment state using the last_seen property. - In the connector creation flow, add a “Provisioning Infrastructure” banner and disable the “Next Step” button if we’re in this state. - In the connector overview, add a warning banner if the infrastructure is not provisioned. - Add a few additional fixes in the “Create Connector” form. ### Screenshots - Create connector form, banner + next button disabled <img width="1265" alt="Screenshot 2025-01-02 at 15 14 56" src="https://github.com/user-attachments/assets/32b224ae-8008-429e-a940-39bf038a03dc" /> - Connector overview banner <img width="1280" alt="Screenshot 2025-01-02 at 15 15 32" src="https://github.com/user-attachments/assets/d29bccf6-b6ed-48ab-9b58-076c3962ad36" /> - Form is non-editable after connector doc is created, don't show API key related info for elastic-managed connector <img width="1296" alt="Screenshot 2025-01-02 at 15 25 56" src="https://github.com/user-attachments/assets/766a1fd7-cef3-4437-8140-4559f8cf5de1" /> ### 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] 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. - [ ] [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]>
1 parent 18fd171 commit 23c28eb

File tree

7 files changed

+211
-94
lines changed

7 files changed

+211
-94
lines changed

x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx

Lines changed: 91 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
9797
setIsModalVisible(false);
9898
};
9999

100+
const showApiKeyInfoForSelfManagedConnector = !connector.is_native;
101+
const showApiKeyBanner = showApiKeyInfoForSelfManagedConnector && apiKey?.encoded;
102+
100103
return (
101104
<>
102105
{isModalVisible && <ConfirmModal onCancel={onCancel} onConfirm={onConfirm} />}
@@ -193,96 +196,99 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
193196
)}
194197
</EuiFlexItem>
195198
<EuiFlexItem />
196-
<EuiFlexItem>
197-
<EuiFlexGroup responsive={false} gutterSize="xs">
199+
{showApiKeyInfoForSelfManagedConnector && (
200+
<>
201+
<EuiFlexItem>
202+
<EuiFlexGroup responsive={false} gutterSize="xs">
203+
<EuiFlexItem grow={false}>
204+
<EuiIcon type="check" />
205+
</EuiFlexItem>
206+
<EuiFlexItem>
207+
{i18n.translate(
208+
'xpack.enterpriseSearch.connectorDeployment.apiKeyCreatedFlexItemLabel',
209+
{ defaultMessage: 'API key created' }
210+
)}
211+
{apiKey?.encoded && ` *`}
212+
</EuiFlexItem>
213+
</EuiFlexGroup>
214+
</EuiFlexItem>
198215
<EuiFlexItem grow={false}>
199-
<EuiIcon type="check" />
216+
<EuiLink
217+
data-test-subj="enterpriseSearchConnectorDeploymentLink"
218+
href={generateEncodedPath(MANAGE_API_KEYS_URL, {})}
219+
external
220+
target="_blank"
221+
>
222+
{apiKey?.name}
223+
</EuiLink>
200224
</EuiFlexItem>
201225
<EuiFlexItem>
202-
{i18n.translate(
203-
'xpack.enterpriseSearch.connectorDeployment.apiKeyCreatedFlexItemLabel',
204-
{ defaultMessage: 'API key created' }
205-
)}
206-
{apiKey?.encoded && ` *`}
207-
</EuiFlexItem>
208-
</EuiFlexGroup>
209-
</EuiFlexItem>
210-
<EuiFlexItem grow={false}>
211-
<EuiLink
212-
data-test-subj="enterpriseSearchConnectorDeploymentLink"
213-
href={generateEncodedPath(MANAGE_API_KEYS_URL, {})}
214-
external
215-
target="_blank"
216-
>
217-
{apiKey?.name}
218-
</EuiLink>
219-
</EuiFlexItem>
220-
<EuiFlexItem>
221-
<EuiFlexGroup
222-
responsive={false}
223-
gutterSize="xs"
224-
justifyContent="flexEnd"
225-
alignItems="center"
226-
>
227-
{apiKey?.encoded ? (
228-
<EuiFlexItem>
229-
<EuiCopy textToCopy={apiKey?.encoded}>
230-
{(copy) => (
231-
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs">
232-
<EuiFlexItem>
233-
<EuiCode>{apiKey?.encoded}</EuiCode>
234-
</EuiFlexItem>
235-
{generateApiKey && (
236-
<EuiFlexItem grow={false}>
237-
<EuiButtonIcon
238-
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
239-
size="xs"
240-
iconType="refresh"
241-
isLoading={isGenerateLoading}
242-
onClick={refreshButtonClick}
243-
disabled={!connector.index_name}
244-
aria-label={i18n.translate(
245-
'xpack.enterpriseSearch.connectorDeployment.refreshAPIKey',
246-
{ defaultMessage: 'Refresh an Elasticsearch API key' }
247-
)}
248-
/>
249-
</EuiFlexItem>
250-
)}
251-
<EuiFlexItem grow={false}>
252-
<EuiButtonIcon
253-
size="xs"
254-
data-test-subj="enterpriseSearchConnectorDeploymentButton"
255-
iconType="copyClipboard"
256-
onClick={copy}
257-
aria-label={i18n.translate(
258-
'xpack.enterpriseSearch.connectorDeployment.copyIndexName',
259-
{ defaultMessage: 'Copy index name' }
226+
<EuiFlexGroup
227+
responsive={false}
228+
gutterSize="xs"
229+
justifyContent="flexEnd"
230+
alignItems="center"
231+
>
232+
{apiKey?.encoded ? (
233+
<EuiFlexItem>
234+
<EuiCopy textToCopy={apiKey?.encoded}>
235+
{(copy) => (
236+
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs">
237+
<EuiFlexItem>
238+
<EuiCode>{apiKey?.encoded}</EuiCode>
239+
</EuiFlexItem>
240+
{generateApiKey && (
241+
<EuiFlexItem grow={false}>
242+
<EuiButtonIcon
243+
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
244+
size="xs"
245+
iconType="refresh"
246+
isLoading={isGenerateLoading}
247+
onClick={refreshButtonClick}
248+
disabled={!connector.index_name}
249+
aria-label={i18n.translate(
250+
'xpack.enterpriseSearch.connectorDeployment.refreshAPIKey',
251+
{ defaultMessage: 'Refresh an Elasticsearch API key' }
252+
)}
253+
/>
254+
</EuiFlexItem>
260255
)}
261-
/>
262-
</EuiFlexItem>
263-
</EuiFlexGroup>
264-
)}
265-
</EuiCopy>
266-
</EuiFlexItem>
267-
) : (
268-
generateApiKey && (
269-
<EuiFlexItem grow={false}>
270-
<EuiButtonIcon
271-
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
272-
size="xs"
273-
iconType="refresh"
274-
isLoading={isGenerateLoading}
275-
onClick={refreshButtonClick}
276-
disabled={!connector.index_name}
277-
/>
278-
</EuiFlexItem>
279-
)
280-
)}
281-
</EuiFlexGroup>
282-
</EuiFlexItem>
256+
<EuiFlexItem grow={false}>
257+
<EuiButtonIcon
258+
size="xs"
259+
data-test-subj="enterpriseSearchConnectorDeploymentButton"
260+
iconType="copyClipboard"
261+
onClick={copy}
262+
aria-label={i18n.translate(
263+
'xpack.enterpriseSearch.connectorDeployment.copyIndexName',
264+
{ defaultMessage: 'Copy index name' }
265+
)}
266+
/>
267+
</EuiFlexItem>
268+
</EuiFlexGroup>
269+
)}
270+
</EuiCopy>
271+
</EuiFlexItem>
272+
) : (
273+
generateApiKey && (
274+
<EuiFlexItem grow={false}>
275+
<EuiButtonIcon
276+
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
277+
size="xs"
278+
iconType="refresh"
279+
isLoading={isGenerateLoading}
280+
onClick={refreshButtonClick}
281+
disabled={!connector.index_name}
282+
/>
283+
</EuiFlexItem>
284+
)
285+
)}
286+
</EuiFlexGroup>
287+
</EuiFlexItem>
288+
</>
289+
)}
283290
</EuiFlexGrid>
284-
285-
{apiKey?.encoded && (
291+
{showApiKeyBanner && (
286292
<>
287293
<EuiSpacer size="m" />
288294
<EuiCallOut

x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ import {
3434
hasDocumentLevelSecurityFeature,
3535
hasIncrementalSyncFeature,
3636
} from '../../utils/connector_helpers';
37-
import { getConnectorLastSeenError, isLastSeenOld } from '../../utils/connector_status_helpers';
37+
import {
38+
getConnectorLastSeenError,
39+
hasConnectorBeenSeenRecently,
40+
isLastSeenOld,
41+
} from '../../utils/connector_status_helpers';
3842

3943
import {
4044
ConnectorNameAndDescriptionActions,
@@ -82,6 +86,7 @@ export interface ConnectorViewValues {
8286
isCanceling: boolean;
8387
isHiddenIndex: boolean;
8488
isLoading: boolean;
89+
isWaitingOnAgentlessDeployment: boolean;
8590
lastUpdated: string | null;
8691
pipelineData: IngestPipelineParams | undefined;
8792
recheckIndexLoading: boolean;
@@ -206,6 +211,7 @@ export const ConnectorViewLogic = kea<MakeLogicType<ConnectorViewValues, Connect
206211
() => [selectors.hasAdvancedFilteringFeature, selectors.hasBasicFilteringFeature],
207212
(advancedFeature: boolean, basicFeature: boolean) => advancedFeature || basicFeature,
208213
],
214+
209215
hasIncrementalSyncFeature: [
210216
() => [selectors.connector],
211217
(connector?: Connector) => hasIncrementalSyncFeature(connector),
@@ -231,6 +237,14 @@ export const ConnectorViewLogic = kea<MakeLogicType<ConnectorViewValues, Connect
231237
[Status.IDLE && Status.LOADING].includes(fetchConnectorApiStatus) ||
232238
(index && [Status.IDLE && Status.LOADING].includes(fetchIndexApiStatus)),
233239
],
240+
isWaitingOnAgentlessDeployment: [
241+
() => [selectors.connector],
242+
(connector: Connector) => {
243+
if (!connector || !connector.is_native) return false;
244+
245+
return !hasConnectorBeenSeenRecently(connector);
246+
},
247+
],
234248
pipelineData: [
235249
() => [selectors.connector],
236250
(connector: Connector | undefined) => connector?.pipeline ?? undefined,

x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,17 @@ import React from 'react';
99

1010
import { useActions, useValues } from 'kea';
1111

12-
import { EuiButton, EuiCallOut, EuiCode, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
12+
import {
13+
EuiButton,
14+
EuiCallOut,
15+
EuiCode,
16+
EuiFlexGroup,
17+
EuiFlexItem,
18+
EuiLink,
19+
EuiLoadingSpinner,
20+
EuiSpacer,
21+
EuiText,
22+
} from '@elastic/eui';
1323

1424
import { i18n } from '@kbn/i18n';
1525

@@ -38,7 +48,7 @@ import { ConnectorViewLogic } from './connector_view_logic';
3848

3949
export const ConnectorDetailOverview: React.FC = () => {
4050
const { indexData } = useValues(IndexViewLogic);
41-
const { connector, error } = useValues(ConnectorViewLogic);
51+
const { connector, error, isWaitingOnAgentlessDeployment } = useValues(ConnectorViewLogic);
4252
const { isCloud } = useValues(KibanaLogic);
4353
const { showModal } = useActions(ConvertConnectorLogic);
4454
const { isModalVisible } = useValues(ConvertConnectorLogic);
@@ -73,6 +83,39 @@ export const ConnectorDetailOverview: React.FC = () => {
7383
</>
7484
)
7585
}
86+
{isWaitingOnAgentlessDeployment && (
87+
<>
88+
<EuiCallOut
89+
color="warning"
90+
title={
91+
<EuiFlexGroup alignItems="center">
92+
<EuiFlexItem grow={false}>
93+
<EuiLoadingSpinner />
94+
</EuiFlexItem>
95+
<EuiFlexItem>
96+
{i18n.translate(
97+
'xpack.enterpriseSearch.content.connectors.overview.agentlessDeploymentNotReadyCallOut.title',
98+
{
99+
defaultMessage: 'Provisioning infrastructure',
100+
}
101+
)}
102+
</EuiFlexItem>
103+
</EuiFlexGroup>
104+
}
105+
>
106+
<EuiSpacer size="s" />
107+
<EuiText size="s">
108+
{i18n.translate(
109+
'xpack.enterpriseSearch.content.connectors.overview.agentlessDeploymentNotReadyCallOut.description',
110+
{
111+
defaultMessage: 'Setting up the agentless infrastructure to run the connector.',
112+
}
113+
)}
114+
</EuiText>
115+
</EuiCallOut>
116+
<EuiSpacer />
117+
</>
118+
)}
76119
{error && (
77120
<>
78121
<EuiCallOut

x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ import { SelfManagePreference } from '../create_connector';
3737

3838
interface ChooseConnectorSelectableProps {
3939
selfManaged: SelfManagePreference;
40+
disabled?: boolean;
4041
}
4142
interface OptionData {
4243
secondaryContent?: string;
4344
}
4445

45-
export const ChooseConnector: React.FC<ChooseConnectorSelectableProps> = ({ selfManaged }) => {
46+
export const ChooseConnector: React.FC<ChooseConnectorSelectableProps> = ({
47+
selfManaged,
48+
disabled,
49+
}) => {
4650
const { euiTheme } = useEuiTheme();
4751
const [selectedOption, setSelectedOption] = useState<Array<EuiComboBoxOptionOption<OptionData>>>(
4852
[]
@@ -142,6 +146,7 @@ export const ChooseConnector: React.FC<ChooseConnectorSelectableProps> = ({ self
142146

143147
return (
144148
<EuiComboBox
149+
isDisabled={disabled}
145150
aria-label={i18n.translate(
146151
'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.euiComboBox.accessibleScreenReaderLabelLabel',
147152
{ defaultMessage: 'Select a data source for your connector to use.' }

0 commit comments

Comments
 (0)