From dbe18e6fd25338674cdbade64a45ea3d8b100cee Mon Sep 17 00:00:00 2001 From: criamico Date: Tue, 5 Jul 2022 18:06:51 +0200 Subject: [PATCH 1/4] [Fleet] Fix syncing issue in Agent flyout --- .../agent_enrollment_flyout/instructions.tsx | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx index 97c0542ab2477..3619f6177cd53 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx @@ -15,8 +15,6 @@ import { FleetServerRequirementPage } from '../../applications/fleet/sections/ag import { AGENTS_PREFIX, FLEET_SERVER_PACKAGE, SO_SEARCH_LIMIT } from '../../constants'; -import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'; - import { Loading } from '..'; import { policyHasFleetServer } from '../../services'; @@ -42,11 +40,16 @@ export const Instructions = (props: InstructionProps) => { refreshAgentPolicies, } = props; const fleetStatus = useFleetStatus(); - const { isUnhealthy: isFleetServerUnhealthy } = useFleetServerUnhealthy(); + const REFRESH_INTERVAL = 10 * 1000; useEffect(() => { - refreshAgentPolicies(); - }, [refreshAgentPolicies]); + const interval = setInterval(() => { + fleetStatus.refresh(); + refreshAgentPolicies(); + }, REFRESH_INTERVAL); + + return () => clearInterval(interval); + }, [fleetStatus, REFRESH_INTERVAL, refreshAgentPolicies]); const fleetServerAgentPolicies: string[] = useMemo( () => agentPolicies.filter((pol) => policyHasFleetServer(pol)).map((pol) => pol.id), @@ -64,27 +67,30 @@ export const Instructions = (props: InstructionProps) => { .join(' or ')}`, }); - const fleetServers = agents?.items || []; + const agentsWithFleetServers = agents?.items || []; - const fleetServerHosts = useMemo(() => { - return settings?.fleet_server_hosts || []; + const hasFleetServerHosts = useMemo(() => { + return (settings?.fleet_server_hosts || []).length > 0; }, [settings]); - if (isLoadingAgents || isLoadingAgentPolicies) return ; - - const hasNoFleetServerHost = fleetStatus.isReady && fleetServerHosts.length === 0; + const showAgentEnrollment = useMemo( + () => hasFleetServerHosts && fleetStatus.isReady && agentsWithFleetServers.length > 0, + [hasFleetServerHosts, fleetStatus.isReady, agentsWithFleetServers.length] + ); - const showAgentEnrollment = - fleetStatus.isReady && - !isFleetServerUnhealthy && - fleetServers.length > 0 && - fleetServerHosts.length > 0; + const showFleetServerEnrollment = useMemo( + () => + !showAgentEnrollment || + (fleetStatus.missingRequirements ?? []).some((r) => r === FLEET_SERVER_PACKAGE), + [fleetStatus.missingRequirements, showAgentEnrollment] + ); - const showFleetServerEnrollment = - fleetServers.length === 0 || - isFleetServerUnhealthy || - (fleetStatus.missingRequirements ?? []).some((r) => r === FLEET_SERVER_PACKAGE); + const hasNoFleetServerHost = useMemo( + () => fleetStatus.isReady && (settings?.fleet_server_hosts || []).length === 0, + [fleetStatus.isReady, settings?.fleet_server_hosts] + ); + if (isLoadingAgents || isLoadingAgentPolicies) return ; if (!isIntegrationFlow && showAgentEnrollment) { setSelectionType('radio'); } else { From 6bd8326bb2e864a8c55084ff4c9cf732959438fd Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 5 Jul 2022 14:24:55 -0400 Subject: [PATCH 2/4] Use context based solution instead of polling --- .../hooks/use_wait_for_fleet_server.ts | 23 +++++----- .../steps/confirm_fleet_server_connection.tsx | 10 +++- .../fleet/sections/agents/index.tsx | 7 ++- .../agent_enrollment_flyout/instructions.tsx | 46 ++++++++----------- .../fleet/public/hooks/use_fleet_status.tsx | 9 +++- 5 files changed, 52 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts index 4da59560e408e..7721025a75067 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; -import { sendGetFleetStatus, useStartServices } from '../../../hooks'; +import { useFleetStatus, useStartServices } from '../../../hooks'; const REFRESH_INTERVAL = 10000; @@ -17,26 +17,25 @@ const REFRESH_INTERVAL = 10000; * in the `missing_requirements` list. */ export const useWaitForFleetServer = () => { - const [isFleetServerReady, setIsFleetServerReady] = useState(false); + const fleetStatus = useFleetStatus(); const { notifications } = useStartServices(); + const isFleetServerReady = + fleetStatus.isReady && !fleetStatus.missingRequirements?.includes('fleet_server'); + useEffect(() => { let interval: ReturnType | null = null; if (!isFleetServerReady) { interval = setInterval(async () => { try { - const res = await sendGetFleetStatus(); - - if (res.error) { - throw res.error; - } - if (res.data?.isReady && !res.data?.missing_requirements?.includes('fleet_server')) { - setIsFleetServerReady(true); - + if (isFleetServerReady) { if (interval) { clearInterval(interval); } + } else { + fleetStatus.setForceDisplayInstructions(true); + fleetStatus.refresh(); } } catch (err) { notifications.toasts.addError(err, { @@ -55,7 +54,7 @@ export const useWaitForFleetServer = () => { }; return cleanup; - }, [notifications.toasts, isFleetServerReady]); + }, [notifications.toasts, isFleetServerReady, fleetStatus]); return { isFleetServerReady }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx index 15e8609191019..8111fd3f09ac6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx @@ -13,7 +13,7 @@ import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useFlyoutContext } from '../../../hooks'; +import { useFleetStatus, useFlyoutContext } from '../../../hooks'; export function getConfirmFleetServerConnectionStep({ disabled, @@ -41,6 +41,12 @@ const ConfirmFleetServerConnectionStepContent: React.FunctionComponent<{ isFleetServerReady: boolean; }> = ({ isFleetServerReady }) => { const flyoutContext = useFlyoutContext(); + const fleetStatus = useFleetStatus(); + + const handleContinueClick = () => { + fleetStatus.forceDisplayInstructions = false; + flyoutContext.openEnrollmentFlyout(); + }; return isFleetServerReady ? ( <> @@ -53,7 +59,7 @@ const ConfirmFleetServerConnectionStepContent: React.FunctionComponent<{ - + { fleetStatus?.missingRequirements?.length === 1 && fleetStatus.missingRequirements[0] === 'fleet_server'; + const displayInstructions = + fleetStatus.forceDisplayInstructions || hasOnlyFleetServerMissingRequirement; + if ( !hasOnlyFleetServerMissingRequirement && fleetStatus.missingRequirements && @@ -86,7 +89,7 @@ export const AgentsApp: React.FunctionComponent = () => { return ; } - const rightColumn = hasOnlyFleetServerMissingRequirement ? ( + const rightColumn = displayInstructions ? ( <> @@ -114,7 +117,7 @@ export const AgentsApp: React.FunctionComponent = () => { {fleetServerModalVisible && ( )} - {hasOnlyFleetServerMissingRequirement ? ( + {displayInstructions ? ( ) : ( diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx index 3619f6177cd53..97c0542ab2477 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx @@ -15,6 +15,8 @@ import { FleetServerRequirementPage } from '../../applications/fleet/sections/ag import { AGENTS_PREFIX, FLEET_SERVER_PACKAGE, SO_SEARCH_LIMIT } from '../../constants'; +import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'; + import { Loading } from '..'; import { policyHasFleetServer } from '../../services'; @@ -40,16 +42,11 @@ export const Instructions = (props: InstructionProps) => { refreshAgentPolicies, } = props; const fleetStatus = useFleetStatus(); - const REFRESH_INTERVAL = 10 * 1000; + const { isUnhealthy: isFleetServerUnhealthy } = useFleetServerUnhealthy(); useEffect(() => { - const interval = setInterval(() => { - fleetStatus.refresh(); - refreshAgentPolicies(); - }, REFRESH_INTERVAL); - - return () => clearInterval(interval); - }, [fleetStatus, REFRESH_INTERVAL, refreshAgentPolicies]); + refreshAgentPolicies(); + }, [refreshAgentPolicies]); const fleetServerAgentPolicies: string[] = useMemo( () => agentPolicies.filter((pol) => policyHasFleetServer(pol)).map((pol) => pol.id), @@ -67,30 +64,27 @@ export const Instructions = (props: InstructionProps) => { .join(' or ')}`, }); - const agentsWithFleetServers = agents?.items || []; + const fleetServers = agents?.items || []; - const hasFleetServerHosts = useMemo(() => { - return (settings?.fleet_server_hosts || []).length > 0; + const fleetServerHosts = useMemo(() => { + return settings?.fleet_server_hosts || []; }, [settings]); - const showAgentEnrollment = useMemo( - () => hasFleetServerHosts && fleetStatus.isReady && agentsWithFleetServers.length > 0, - [hasFleetServerHosts, fleetStatus.isReady, agentsWithFleetServers.length] - ); + if (isLoadingAgents || isLoadingAgentPolicies) return ; - const showFleetServerEnrollment = useMemo( - () => - !showAgentEnrollment || - (fleetStatus.missingRequirements ?? []).some((r) => r === FLEET_SERVER_PACKAGE), - [fleetStatus.missingRequirements, showAgentEnrollment] - ); + const hasNoFleetServerHost = fleetStatus.isReady && fleetServerHosts.length === 0; - const hasNoFleetServerHost = useMemo( - () => fleetStatus.isReady && (settings?.fleet_server_hosts || []).length === 0, - [fleetStatus.isReady, settings?.fleet_server_hosts] - ); + const showAgentEnrollment = + fleetStatus.isReady && + !isFleetServerUnhealthy && + fleetServers.length > 0 && + fleetServerHosts.length > 0; + + const showFleetServerEnrollment = + fleetServers.length === 0 || + isFleetServerUnhealthy || + (fleetStatus.missingRequirements ?? []).some((r) => r === FLEET_SERVER_PACKAGE); - if (isLoadingAgents || isLoadingAgentPolicies) return ; if (!isIntegrationFlow && showAgentEnrollment) { setSelectionType('radio'); } else { diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx index b05df0c619e0a..85df40a31d801 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx @@ -23,17 +23,22 @@ interface FleetStatusState { interface FleetStatus extends FleetStatusState { refresh: () => Promise; + forceDisplayInstructions: boolean; + setForceDisplayInstructions: React.Dispatch; } const FleetStatusContext = React.createContext(undefined); export const FleetStatusProvider: React.FC = ({ children }) => { const config = useConfig(); + const [forceDisplayInstructions, setForceDisplayInstructions] = useState(false); + const [state, setState] = useState({ enabled: config.agents.enabled, isLoading: false, isReady: false, }); + const sendGetStatus = useCallback( async function sendGetStatus() { try { @@ -64,7 +69,9 @@ export const FleetStatusProvider: React.FC = ({ children }) => { const refresh = useCallback(() => sendGetStatus(), [sendGetStatus]); return ( - + {children} ); From 78f7eec3cba3766c8221dcd9ce977582a016dc7b Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 5 Jul 2022 14:29:56 -0400 Subject: [PATCH 3/4] Add clarifying comment --- x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx index 85df40a31d801..7319cd9f07be7 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx @@ -23,6 +23,10 @@ interface FleetStatusState { interface FleetStatus extends FleetStatusState { refresh: () => Promise; + + // This flag allows us to opt into displaying the Fleet Server enrollment instructions even if + // a healthy Fleet Server has been detected, so we can delay removing the enrollment UI until + // some user action like clicking a "continue" button forceDisplayInstructions: boolean; setForceDisplayInstructions: React.Dispatch; } From 5f8d45abd90371b07a901587d2938b4b39f14092 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 5 Jul 2022 14:54:23 -0400 Subject: [PATCH 4/4] Fix types --- .../applications/fleet/sections/agents/index.test.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx index 35353b9aaae3d..7139cabed4f6f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx @@ -69,6 +69,8 @@ describe('AgentApp', () => { enabled: true, isReady: false, refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); @@ -82,6 +84,8 @@ describe('AgentApp', () => { isReady: false, missingRequirements: ['api_keys'], refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); expect(utils.queryByText('MissingESRequirementsPage')).not.toBeNull(); @@ -95,6 +99,8 @@ describe('AgentApp', () => { isReady: false, missingRequirements: ['fleet_server'], refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); expect(utils.queryByText('FleetServerRequirementPage')).not.toBeNull(); @@ -109,6 +115,8 @@ describe('AgentApp', () => { missingRequirements: [], missingOptionalFeatures: ['encrypted_saved_object_encryption_key_required'], refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp();