From f2fb3ee768f62aca11a6b3adae0514e1b1f56fcd Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Thu, 25 Jan 2024 12:29:25 +0100 Subject: [PATCH] [Fleet] Fix incorrect count of agents in bulk actions (#175318) Fixes https://github.com/elastic/kibana/issues/171914 ## Summary Fix a small issue occurring when there are inactive agents, both regular and managed. Another PR with some refactoring for this component will follow. ### Repro steps - Enroll fleet server on fleet-server-policy - make sure to have two inactive fleet server agents on this policy as well - Enroll ~20 agents with horde and make them inactive - Make fleet server policy "managed" - Select "inactive" in the status filter - Click on "Select agents on all pages" - The bulk actions shouldn't report negative actions. If the count is 0, the actions will appear disabled #### Before ![Screenshot 2024-01-22 at 15 59 42](https://github.com/elastic/kibana/assets/16084106/cb8c6c80-b23f-4415-af2a-b4de8ae387f6) #### After ![Screenshot 2024-01-24 at 15 53 53](https://github.com/elastic/kibana/assets/16084106/6b020016-6171-43d7-bc12-685bf01b7989) ### Checklist - [ ] [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/bulk_actions.test.tsx | 53 +++++++++++++++++-- .../components/bulk_actions.tsx | 15 +++--- .../components/search_and_filter_bar.tsx | 3 ++ .../sections/agents/agent_list_page/index.tsx | 6 +++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx index 20fb4bb83dac8..5834c776a6dce 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx @@ -30,6 +30,7 @@ const defaultProps = { shownAgents: 10, inactiveShownAgents: 0, totalManagedAgentIds: [], + inactiveManagedAgentIds: [], selectionMode: 'manual', currentQuery: '', selectedAgents: [], @@ -126,6 +127,10 @@ describe('AgentBulkActions', () => { }); describe('When in query mode', () => { + mockedExperimentalFeaturesService.get.mockReturnValue({ + diagnosticFileUploadEnabled: true, + } as any); + it('should show correct actions for active agents when no managed policies exist', async () => { const results = render({ ...defaultProps, @@ -162,15 +167,57 @@ describe('AgentBulkActions', () => { expect(results.getByText('Add / remove tags').closest('button')!).toBeEnabled(); expect(results.getByText('Assign to new policy').closest('button')!).toBeEnabled(); - expect(results.getByText('Unenroll 8 agents').closest('button')!).toBeEnabled(); - expect(results.getByText('Upgrade 8 agents').closest('button')!).toBeEnabled(); - expect(results.getByText('Schedule upgrade for 8 agents').closest('button')!).toBeDisabled(); expect( results.getByText('Request diagnostics for 8 agents').closest('button')! ).toBeEnabled(); + expect(results.getByText('Unenroll 8 agents').closest('button')!).toBeEnabled(); + expect(results.getByText('Upgrade 8 agents').closest('button')!).toBeEnabled(); + expect(results.getByText('Schedule upgrade for 8 agents').closest('button')!).toBeDisabled(); expect(results.getByText('Restart upgrade 8 agents').closest('button')!).toBeEnabled(); }); + it('should show correct actions also when there are inactive managed agents', async () => { + const results = render({ + ...defaultProps, + inactiveManagedAgentIds: ['agentId1', 'agentId2'], + totalManagedAgentIds: ['agentId1', 'agentId2', 'agentId3'], + selectionMode: 'query', + }); + + const bulkActionsButton = results.getByTestId('agentBulkActionsButton'); + await act(async () => { + fireEvent.click(bulkActionsButton); + }); + + expect(results.getByText('Add / remove tags').closest('button')!).toBeEnabled(); + expect(results.getByText('Assign to new policy').closest('button')!).toBeEnabled(); + expect(results.getByText('Unenroll 9 agents').closest('button')!).toBeEnabled(); + expect(results.getByText('Upgrade 9 agents').closest('button')!).toBeEnabled(); + expect(results.getByText('Schedule upgrade for 9 agents').closest('button')!).toBeDisabled(); + expect(results.getByText('Restart upgrade 9 agents').closest('button')!).toBeEnabled(); + }); + + it('should show disabled actions when only inactive agents are selected', async () => { + const results = render({ + ...defaultProps, + inactiveShownAgents: 10, + selectedAgents: [{ id: 'agent1' }, { id: 'agent2' }] as Agent[], + selectionMode: 'query', + }); + + const bulkActionsButton = results.getByTestId('agentBulkActionsButton'); + await act(async () => { + fireEvent.click(bulkActionsButton); + }); + + expect(results.getByText('Add / remove tags').closest('button')!).toBeDisabled(); + expect(results.getByText('Assign to new policy').closest('button')!).toBeDisabled(); + expect(results.getByText('Unenroll 0 agents').closest('button')!).toBeDisabled(); + expect(results.getByText('Upgrade 0 agents').closest('button')!).toBeDisabled(); + expect(results.getByText('Schedule upgrade for 0 agents').closest('button')!).toBeDisabled(); + expect(results.getByText('Restart upgrade 0 agents').closest('button')!).toBeDisabled(); + }); + it('should generate a correct kuery to select agents', async () => { const results = render({ ...defaultProps, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index 6ae366f1dbb98..cd00c194a6c11 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -38,6 +38,7 @@ export interface Props { shownAgents: number; inactiveShownAgents: number; totalManagedAgentIds: string[]; + inactiveManagedAgentIds: string[]; selectionMode: SelectionMode; currentQuery: string; selectedAgents: Agent[]; @@ -51,6 +52,7 @@ export const AgentBulkActions: React.FunctionComponent = ({ shownAgents, inactiveShownAgents, totalManagedAgentIds, + inactiveManagedAgentIds, selectionMode, currentQuery, selectedAgents, @@ -91,18 +93,19 @@ export const AgentBulkActions: React.FunctionComponent = ({ } }, [currentQuery, totalManagedAgentIds]); - // Check if user is working with only inactive agents - const atLeastOneActiveAgentSelected = - selectionMode === 'manual' - ? !!selectedAgents.find((agent) => agent.active) - : shownAgents > inactiveShownAgents; const totalActiveAgents = shownAgents - inactiveShownAgents; + // exclude inactive agents from the count const agentCount = selectionMode === 'manual' ? selectedAgents.length - : totalActiveAgents - totalManagedAgentIds?.length; + : totalActiveAgents - (totalManagedAgentIds?.length - inactiveManagedAgentIds?.length); + // Check if user is working with only inactive agents + const atLeastOneActiveAgentSelected = + selectionMode === 'manual' + ? !!selectedAgents.find((agent) => agent.active) + : agentCount > 0 && shownAgents > inactiveShownAgents; const agents = selectionMode === 'manual' ? selectedAgents : selectionQuery; const [tagsPopoverButton, setTagsPopoverButton] = useState(); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index 3ecc5e6545795..8013a490103af 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -49,6 +49,7 @@ export interface SearchAndFilterBarProps { inactiveShownAgents: number; totalInactiveAgents: number; totalManagedAgentIds: string[]; + inactiveManagedAgentIds: string[]; selectionMode: SelectionMode; currentQuery: string; selectedAgents: Agent[]; @@ -78,6 +79,7 @@ export const SearchAndFilterBar: React.FunctionComponent = () => { const [inactiveShownAgents, setInactiveShownAgents] = useState(0); const [totalInactiveAgents, setTotalInactiveAgents] = useState(0); const [totalManagedAgentIds, setTotalManagedAgentIds] = useState([]); + const [inactiveManagedAgentIds, setInactiveManagedAgentIds] = useState([]); const [managedAgentsOnCurrentPage, setManagedAgentsOnCurrentPage] = useState(0); const [showAgentActivityTour, setShowAgentActivityTour] = useState({ isOpen: false }); const getSortFieldForAPI = (field: keyof Agent): string => { @@ -335,7 +336,11 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { } const allManagedAgents = response.data?.items ?? []; const allManagedAgentIds = allManagedAgents?.map((agent) => agent.id); + const inactiveManagedIds = allManagedAgents + ?.filter((agent) => agent.status === 'inactive') + .map((agent) => agent.id); setTotalManagedAgentIds(allManagedAgentIds); + setInactiveManagedAgentIds(inactiveManagedIds); setManagedAgentsOnCurrentPage( agentsResponse.data.items @@ -606,6 +611,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { inactiveShownAgents={inactiveShownAgents} totalInactiveAgents={totalInactiveAgents} totalManagedAgentIds={totalManagedAgentIds} + inactiveManagedAgentIds={inactiveManagedAgentIds} selectionMode={selectionMode} currentQuery={kuery} selectedAgents={selectedAgents}