From 23c28eb712509beff296e1db5a0c62172cad07cb Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Tue, 7 Jan 2025 12:41:26 +0100 Subject: [PATCH] [Connectors] Detect deploying agentless infra state (#205395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 (https://github.com/elastic/connectors/pull/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 Screenshot 2025-01-02 at 15 14 56 - Connector overview banner Screenshot 2025-01-02 at 15 15 32 - Form is non-editable after connector doc is created, don't show API key related info for elastic-managed connector Screenshot 2025-01-02 at 15 25 56 ### 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 --- .../components/generated_config_fields.tsx | 176 +++++++++--------- .../connector_detail/connector_view_logic.ts | 16 +- .../components/connector_detail/overview.tsx | 47 ++++- .../components/choose_connector.tsx | 7 +- .../create_connector/configuration_step.tsx | 40 +++- .../create_connector/start_step.tsx | 7 +- .../utils/connector_status_helpers.ts | 12 ++ 7 files changed, 211 insertions(+), 94 deletions(-) diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx index 53cbf579a940f..a905242c90fec 100644 --- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx +++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx @@ -97,6 +97,9 @@ export const GeneratedConfigFields: React.FC = ({ setIsModalVisible(false); }; + const showApiKeyInfoForSelfManagedConnector = !connector.is_native; + const showApiKeyBanner = showApiKeyInfoForSelfManagedConnector && apiKey?.encoded; + return ( <> {isModalVisible && } @@ -193,96 +196,99 @@ export const GeneratedConfigFields: React.FC = ({ )} - - + {showApiKeyInfoForSelfManagedConnector && ( + <> + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.apiKeyCreatedFlexItemLabel', + { defaultMessage: 'API key created' } + )} + {apiKey?.encoded && ` *`} + + + - + + {apiKey?.name} + - {i18n.translate( - 'xpack.enterpriseSearch.connectorDeployment.apiKeyCreatedFlexItemLabel', - { defaultMessage: 'API key created' } - )} - {apiKey?.encoded && ` *`} - - - - - - {apiKey?.name} - - - - - {apiKey?.encoded ? ( - - - {(copy) => ( - - - {apiKey?.encoded} - - {generateApiKey && ( - - - - )} - - + {apiKey?.encoded ? ( + + + {(copy) => ( + + + {apiKey?.encoded} + + {generateApiKey && ( + + + )} - /> - - - )} - - - ) : ( - generateApiKey && ( - - - - ) - )} - - + + + + + )} + + + ) : ( + generateApiKey && ( + + + + ) + )} + + + + )} - - {apiKey?.encoded && ( + {showApiKeyBanner && ( <> [selectors.hasAdvancedFilteringFeature, selectors.hasBasicFilteringFeature], (advancedFeature: boolean, basicFeature: boolean) => advancedFeature || basicFeature, ], + hasIncrementalSyncFeature: [ () => [selectors.connector], (connector?: Connector) => hasIncrementalSyncFeature(connector), @@ -231,6 +237,14 @@ export const ConnectorViewLogic = kea [selectors.connector], + (connector: Connector) => { + if (!connector || !connector.is_native) return false; + + return !hasConnectorBeenSeenRecently(connector); + }, + ], pipelineData: [ () => [selectors.connector], (connector: Connector | undefined) => connector?.pipeline ?? undefined, diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx index 3fdd3d379eacb..9024f67858d33 100644 --- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx +++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx @@ -9,7 +9,17 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiButton, EuiCallOut, EuiCode, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiLoadingSpinner, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -38,7 +48,7 @@ import { ConnectorViewLogic } from './connector_view_logic'; export const ConnectorDetailOverview: React.FC = () => { const { indexData } = useValues(IndexViewLogic); - const { connector, error } = useValues(ConnectorViewLogic); + const { connector, error, isWaitingOnAgentlessDeployment } = useValues(ConnectorViewLogic); const { isCloud } = useValues(KibanaLogic); const { showModal } = useActions(ConvertConnectorLogic); const { isModalVisible } = useValues(ConvertConnectorLogic); @@ -73,6 +83,39 @@ export const ConnectorDetailOverview: React.FC = () => { ) } + {isWaitingOnAgentlessDeployment && ( + <> + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.connectors.overview.agentlessDeploymentNotReadyCallOut.title', + { + defaultMessage: 'Provisioning infrastructure', + } + )} + + + } + > + + + {i18n.translate( + 'xpack.enterpriseSearch.content.connectors.overview.agentlessDeploymentNotReadyCallOut.description', + { + defaultMessage: 'Setting up the agentless infrastructure to run the connector.', + } + )} + + + + + )} {error && ( <> = ({ selfManaged }) => { +export const ChooseConnector: React.FC = ({ + selfManaged, + disabled, +}) => { const { euiTheme } = useEuiTheme(); const [selectedOption, setSelectedOption] = useState>>( [] @@ -142,6 +146,7 @@ export const ChooseConnector: React.FC = ({ self return ( = ({ title, setCurrentStep }) => { - const { connector } = useValues(ConnectorViewLogic); + const { connector, isWaitingOnAgentlessDeployment } = useValues(ConnectorViewLogic); const { updateConnectorConfiguration } = useActions(ConnectorViewLogic); const { setFormDirty } = useActions(NewConnectorLogic); const { overlays } = useKibana().services; @@ -46,10 +48,12 @@ export const ConfigurationStep: React.FC = ({ title, set const { status } = useValues(ConnectorConfigurationApiLogic); const isSyncing = false; - const isNextStepEnabled = + const isConnectorConfigured = connector?.status === ConnectorStatus.CONNECTED || connector?.status === ConnectorStatus.CONFIGURED; + const isNextStepEnabled = !isWaitingOnAgentlessDeployment && isConnectorConfigured; + useEffect(() => { setTimeout(() => { window.scrollTo({ @@ -64,6 +68,37 @@ export const ConfigurationStep: React.FC = ({ title, set return ( <> + {isWaitingOnAgentlessDeployment && ( + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.configurationStep.agentlessDeploymentNotReadyCallOut.title', + { + defaultMessage: 'Provisioning infrastructure', + } + )} + + + } + > + + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.configurationStep.agentlessDeploymentNotReadyCallOut.description', + { + defaultMessage: + 'Setting up the agentless infrastructure to run the connector. This process may take up to one minute.', + } + )} + + + )} @@ -114,6 +149,7 @@ export const ConfigurationStep: React.FC = ({ title, set { if (isFormEditing) { diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx index 978a87fec1220..c0d7a2af0a682 100644 --- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx +++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx @@ -107,7 +107,7 @@ export const StartStep: React.FC = ({ { defaultMessage: 'Connector' } )} > - + @@ -138,7 +138,7 @@ export const StartStep: React.FC = ({ = ({ } > diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_status_helpers.ts b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_status_helpers.ts index c9e2bb0dd2b45..115601749f1c8 100644 --- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_status_helpers.ts +++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_status_helpers.ts @@ -26,6 +26,18 @@ export const getConnectorLastSeenError = (connector: Connector): string => { ); }; +// Determines if the connector has been seen recently. +// Note: The default heartbeat interval for the connector service is every 5 minutes. +// This is configured using the `service.heartbeat` key in: +// https://github.com/elastic/connectors/blob/main/connectors/config.py +export const hasConnectorBeenSeenRecently = ( + connector: Connector, + timeWindowMinutes: number = 10 +): boolean => + connector.last_seen + ? moment(connector.last_seen).isSameOrAfter(moment().subtract(timeWindowMinutes, 'minutes')) + : false; + const incompleteText = i18n.translate( 'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.incomplete.label', { defaultMessage: 'Incomplete' }