From b7f44471aa3d1c081d585d97c7ddb002e7b6f8c7 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 26 Sep 2022 07:36:19 -0600 Subject: [PATCH] [Fleet] added cardinality agg when counting acks in action_status (#141651) (#141764) * added cardinality agg when counting acks in action_status * added precision_threshold, added tests for activity flyout * fixed tests * fixed tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit adffaa48d611051301d581d40280963928aeea0a) Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> --- .../components/agent_activity_flyout.test.tsx | 316 ++++++++++++++++++ .../components/agent_activity_flyout.tsx | 26 +- .../server/services/agents/action_status.ts | 8 +- 3 files changed, 341 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx new file mode 100644 index 0000000000000..68a3420780039 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.test.tsx @@ -0,0 +1,316 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act, render, fireEvent } from '@testing-library/react'; +// eslint-disable-next-line @kbn/eslint/module_migration +import { IntlProvider } from 'react-intl'; + +import { useActionStatus } from '../hooks'; +import { useGetAgentPolicies, useStartServices } from '../../../../hooks'; + +import { AgentActivityFlyout } from './agent_activity_flyout'; + +jest.mock('../hooks'); +jest.mock('../../../../hooks'); + +const mockUseActionStatus = useActionStatus as jest.Mock; +const mockUseGetAgentPolicies = useGetAgentPolicies as jest.Mock; +const mockUseStartServices = useStartServices as jest.Mock; + +describe('AgentActivityFlyout', () => { + const mockOnClose = jest.fn(); + const mockOnAbortSuccess = jest.fn(); + const mockAbortUpgrade = jest.fn(); + + beforeEach(() => { + mockOnClose.mockReset(); + mockOnAbortSuccess.mockReset(); + mockAbortUpgrade.mockReset(); + mockUseActionStatus.mockReset(); + mockUseGetAgentPolicies.mockReturnValue({ + data: { + items: [ + { id: 'policy1', name: 'Policy 1' }, + { id: 'policy2', name: 'Policy 2' }, + ], + }, + }); + mockUseStartServices.mockReturnValue({ + docLinks: { links: { fleet: { upgradeElasticAgent: 'https://elastic.co' } } }, + }); + }); + + beforeEach(() => { + jest.useFakeTimers('modern').setSystemTime(new Date('2022-09-15T10:00:00.000Z')); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const renderComponent = () => { + return render( + + + + ); + }; + + it('should render agent activity for in progress upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.getByText('Agent activity')).toBeInTheDocument(); + + expect( + result.container.querySelector('[data-test-subj="upgradeInProgressTitle"]')!.textContent + ).toEqual('Upgrading 5 agents to version 8.5.0'); + // compare without whitespace,   doesn't match + expect( + result.container + .querySelector('[data-test-subj="upgradeInProgressDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Started on Sep 15, 2022 10:00 AM. Learn more'.replace(/\s/g, '')); + + act(() => { + fireEvent.click(result.getByText('Abort upgrade')); + }); + + expect(mockAbortUpgrade).toHaveBeenCalled(); + }); + + it('should render agent activity for scheduled upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-16T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-17T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.getByText('Agent activity')).toBeInTheDocument(); + + expect( + result.container.querySelector('[data-test-subj="upgradeInProgressTitle"]')!.textContent + ).toEqual('5 agents scheduled to upgrade to version 8.5.0'); + expect( + result.container + .querySelector('[data-test-subj="upgradeInProgressDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Scheduled for Sep 16, 2022 10:00 AM. Learn more'.replace(/\s/g, '')); + + act(() => { + fireEvent.click(result.getByText('Abort upgrade')); + }); + + expect(mockAbortUpgrade).toHaveBeenCalled(); + }); + + it('should render agent activity for complete upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action3', + nbAgentsActionCreated: 2, + nbAgentsAck: 2, + type: 'UPGRADE', + nbAgentsActioned: 2, + status: 'COMPLETE', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + completionTime: '2022-09-15T12:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '2 agents upgraded' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Completed Sep 15, 2022 12:00 PM'.replace(/\s/g, '')); + }); + + it('should render agent activity for expired unenroll', () => { + const mockActionStatuses = [ + { + actionId: 'action4', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNENROLL', + nbAgentsActioned: 3, + status: 'EXPIRED', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + 'Agent unenrollment expired' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Expired on Sep 14, 2022 10:00 AM'.replace(/\s/g, '')); + }); + + it('should render agent activity for cancelled upgrade', () => { + const mockActionStatuses = [ + { + actionId: 'action5', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 3, + status: 'CANCELLED', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + cancellationTime: '2022-09-15T11:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + 'Agent upgrade cancelled' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Cancelled on Sep 15, 2022 11:00 AM'.replace(/\s/g, '')); + }); + + it('should render agent activity for failed reassign', () => { + const mockActionStatuses = [ + { + actionId: 'action7', + nbAgentsActionCreated: 1, + nbAgentsAck: 0, + type: 'POLICY_REASSIGN', + nbAgentsActioned: 1, + status: 'FAILED', + expiration: '2099-09-16T10:00:00.000Z', + newPolicyId: 'policy1', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 1, + completionTime: '2022-09-15T11:00:00.000Z', + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '0 of 1 agent assigned to a new policy' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain( + 'A problem occurred during this operation. Started on Sep 15, 2022 10:00 AM.'.replace( + /\s/g, + '' + ) + ); + }); + + it('should render agent activity for unknown action', () => { + const mockActionStatuses = [ + { + actionId: 'action8', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNKNOWN', + nbAgentsActioned: 3, + status: 'COMPLETE', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + completionTime: '2022-09-15T12:00:00.000Z', + nbAgentsFailed: 0, + }, + ]; + mockUseActionStatus.mockReturnValue({ + currentActions: mockActionStatuses, + abortUpgrade: mockAbortUpgrade, + isFirstLoading: true, + }); + const result = renderComponent(); + + expect(result.container.querySelector('[data-test-subj="statusTitle"]')!.textContent).toEqual( + '0 of 3 agents actioned' + ); + expect( + result.container + .querySelector('[data-test-subj="statusDescription"]')! + .textContent?.replace(/\s/g, '') + ).toContain('Completed Sep 15, 2022 12:00 PM'.replace(/\s/g, '')); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index 929eb4bbf54d1..dfa6623f4a90a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -176,6 +176,7 @@ export const AgentActivityFlyout: React.FunctionComponent<{ ) : null} {Object.keys(otherDays).map((day) => ( } actions={otherDays[day]} abortUpgrade={abortUpgrade} @@ -215,9 +216,13 @@ const ActivitySection: React.FunctionComponent<{ {actions.map((currentAction) => currentAction.type === 'UPGRADE' && currentAction.status === 'IN_PROGRESS' ? ( - + ) : ( - + ) )} @@ -272,7 +277,7 @@ const inProgressTitle = (action: ActionStatus) => ( defaultMessage="{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}" values={{ nbAgents: - action.nbAgentsAck === action.nbAgentsActioned + action.nbAgentsAck >= action.nbAgentsActioned ? action.nbAgentsAck : action.nbAgentsAck === 0 ? action.nbAgentsActioned @@ -438,14 +443,19 @@ const ActivityItem: React.FunctionComponent<{ action: ActionStatus }> = ({ actio {displayByStatus[action.status].icon} - + {displayByStatus[action.status].title} - {displayByStatus[action.status].description} + + {displayByStatus[action.status].description} + @@ -486,7 +496,7 @@ export const UpgradeInProgressActivityItem: React.FunctionComponent<{ {isScheduled ? : } - + {isScheduled && action.startTime ? ( - +

{isScheduled && action.startTime ? ( <> @@ -541,7 +551,7 @@ export const UpgradeInProgressActivityItem: React.FunctionComponent<{ size="s" onClick={onClickAbortUpgrade} isLoading={isAborting} - data-test-subj="currentBulkUpgrade.abortBtn" + data-test-subj="abortBtn" > bucket.key === action.actionId ); - const nbAgentsAck = matchingBucket?.doc_count ?? 0; + const nbAgentsAck = (matchingBucket?.agent_count as any)?.value ?? 0; const completionTime = (matchingBucket?.max_timestamp as any)?.value_as_string; const nbAgentsActioned = action.nbAgentsActioned || action.nbAgentsActionCreated; const complete = nbAgentsAck >= nbAgentsActioned;