From 6c3ecd7fbaccb6d3d60495cbbaf0dabd19c7db9a Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Thu, 3 Oct 2024 14:21:56 +0200 Subject: [PATCH] [Fleet] Display missing warning when triggering delete package policies actions (#194808) Fixes https://github.com/elastic/kibana/issues/190476 ## Summary Display missing warning when triggering delete package policies actions. Currently when no agents are enrolled this warning doesn't get displayed. - Also added unit tests for this action provider component ![Screenshot 2024-10-03 at 12 26 59](https://github.com/user-attachments/assets/a1fa7753-f061-4f87-a0ba-54253690903a) Video showing the warnings in different cases. The case the was fixed is the first one: https://github.com/user-attachments/assets/0fe9d1d2-c6fd-4f8c-84ad-1cce20ed7eac ### Testing - You need to have an integration policy shared between several agent policies and those agent policies shouldn't have any agents enrolled. - Go to integration policy page and try to delete the policy from "delete integration" - The warning above should appear ### 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 --- .../package_policy_delete_provider.test.tsx | 260 ++++++++++++++++++ .../package_policy_delete_provider.tsx | 34 +-- 2 files changed, 278 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/fleet/public/components/package_policy_delete_provider.test.tsx diff --git a/x-pack/plugins/fleet/public/components/package_policy_delete_provider.test.tsx b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.test.tsx new file mode 100644 index 0000000000000..720218731136c --- /dev/null +++ b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.test.tsx @@ -0,0 +1,260 @@ +/* + * 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, fireEvent } from '@testing-library/react'; + +import { EuiContextMenuItem } from '@elastic/eui'; + +import type { AgentPolicy, PackagePolicy } from '../types'; +import { createIntegrationsTestRendererMock } from '../mock'; + +import { sendGetAgents, useMultipleAgentPolicies } from '../hooks'; + +import { PackagePolicyDeleteProvider } from './package_policy_delete_provider'; + +jest.mock('../hooks', () => { + return { + ...jest.requireActual('../hooks'), + useMultipleAgentPolicies: jest.fn(), + useStartServices: jest.fn().mockReturnValue({ + notifications: { + toasts: { addSuccess: jest.fn() }, + }, + }), + sendGetAgents: jest.fn(), + useConfig: jest.fn().mockReturnValue({ + agents: { enabled: true }, + }), + }; +}); + +const useMultipleAgentPoliciesMock = useMultipleAgentPolicies as jest.MockedFunction< + typeof useMultipleAgentPolicies +>; +const sendGetAgentsMock = sendGetAgents as jest.MockedFunction; + +function renderMenu({ + agentPolicies, + packagePolicyIds, +}: { + agentPolicies: AgentPolicy[]; + packagePolicyIds: string[]; +}) { + const renderer = createIntegrationsTestRendererMock(); + + const utils = renderer.render( + + {(deletePackagePoliciesPrompt) => { + return ( + { + deletePackagePoliciesPrompt(packagePolicyIds, () => {}); + }} + > + Delete integration + + ); + }} + + ); + + return { utils }; +} + +function createMockAgentPolicies( + props: Partial = {}, + multiple?: boolean +): AgentPolicy[] { + if (!multiple) { + return [ + { + id: 'some-uuid1', + namespace: 'default', + monitoring_enabled: [], + name: 'Test Policy', + description: '', + is_preconfigured: false, + status: 'active', + is_managed: false, + revision: 1, + updated_at: '', + updated_by: 'elastic', + package_policies: [ + { name: 'integration-0001' } as PackagePolicy, + { name: 'integration-0002' } as PackagePolicy, + ], + is_protected: false, + ...props, + }, + ]; + } else { + return [ + { + id: 'some-uuid1', + namespace: 'default', + monitoring_enabled: [], + name: 'Test Policy 1', + description: '', + is_preconfigured: false, + status: 'active', + is_managed: false, + revision: 1, + updated_at: '', + updated_by: 'elastic', + package_policies: [], + is_protected: false, + ...props, + }, + { + id: 'some-uuid2', + namespace: 'default', + monitoring_enabled: [], + name: 'Test Policy 2', + description: '', + is_preconfigured: false, + status: 'active', + is_managed: false, + revision: 1, + updated_at: '', + updated_by: 'elastic', + package_policies: [ + { name: 'integration-0001' } as PackagePolicy, + { name: 'integration-0002' } as PackagePolicy, + ], + is_protected: false, + ...props, + }, + ]; + } +} + +describe('PackagePolicyDeleteProvider', () => { + it('Should show delete integrations action and cancel modal', async () => { + useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: false }); + sendGetAgentsMock.mockResolvedValue({ + data: { + statusSummary: {}, + items: [ + { + id: 'agent123', + policy_id: 'agent-policy-1', + }, + ], + total: 5, + }, + } as any); + const agentPolicies = createMockAgentPolicies(); + const { utils } = renderMenu({ + agentPolicies, + packagePolicyIds: ['integration-0001'], + }); + await act(async () => { + const button = utils.getByRole('button'); + fireEvent.click(button); + }); + expect(utils.getByText('This action will affect 5 agents.')).toBeInTheDocument(); + expect( + utils.getByText('This action can not be undone. Are you sure you wish to continue?') + ).toBeInTheDocument(); + expect(utils.getAllByText(/is already in use by some of your agents./).length).toBe(1); + }); + + it('When multiple agent policies are present and agents are enrolled show additional warnings', async () => { + sendGetAgentsMock.mockResolvedValue({ + data: { + statusSummary: {}, + items: [ + { + id: 'agent123', + policy_id: 'agent-policy-1', + }, + ], + total: 5, + }, + } as any); + useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: true }); + const agentPolicies = createMockAgentPolicies(undefined, true); + const { utils } = renderMenu({ + agentPolicies, + packagePolicyIds: ['integration-0001'], + }); + await act(async () => { + const button = utils.getByRole('button'); + fireEvent.click(button); + }); + expect(utils.getByText('This action will affect 5 agents.')).toBeInTheDocument(); + expect( + utils.getByText('This integration is shared by multiple agent policies.') + ).toBeInTheDocument(); + expect( + utils.getByText('This action can not be undone. Are you sure you wish to continue?') + ).toBeInTheDocument(); + expect(utils.queryAllByText(/is already in use by some of your agents./).length).toBe(0); + }); + + it('When multiple agent policies are present and no agents are enrolled show additional warnings', async () => { + sendGetAgentsMock.mockResolvedValue({ + data: { + statusSummary: {}, + items: [], + total: 0, + }, + } as any); + useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: true }); + const agentPolicies = createMockAgentPolicies(undefined, true); + const { utils } = renderMenu({ + agentPolicies, + packagePolicyIds: ['integration-0001'], + }); + await act(async () => { + const button = utils.getByRole('button'); + fireEvent.click(button); + }); + expect(utils.queryByText('This action will affect 5 agents.')).not.toBeInTheDocument(); + expect(utils.queryAllByText(/is already in use by some of your agents./).length).toBe(0); + expect( + utils.getByText('This integration is shared by multiple agent policies.') + ).toBeInTheDocument(); + expect( + utils.getByText('This action can not be undone. Are you sure you wish to continue?') + ).toBeInTheDocument(); + }); + + it('When agentless should show a different set of warnings', async () => { + useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: false }); + sendGetAgentsMock.mockResolvedValue({ + data: { + statusSummary: {}, + items: [ + { + id: 'agent123', + policy_id: 'agent-policy-1', + }, + ], + total: 5, + }, + } as any); + const agentPolicies = createMockAgentPolicies({ supports_agentless: true }); + const { utils } = renderMenu({ + agentPolicies, + packagePolicyIds: ['integration-0001'], + }); + await act(async () => { + const button = utils.getByRole('button'); + fireEvent.click(button); + }); + // utils.debug(); + expect(utils.queryByText('This action will affect 5 agents.')).not.toBeInTheDocument(); + expect(utils.getByText(/about to delete an integration/)).toBeInTheDocument(); + expect( + utils.getByText('This action can not be undone. Are you sure you wish to continue?') + ).toBeInTheDocument(); + expect(utils.getAllByText(/integration will stop data ingestion./).length).toBe(1); + }); +}); diff --git a/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx index 7d71915fda252..f12c4cf371ed5 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx @@ -15,10 +15,11 @@ import { sendDeletePackagePolicy, sendDeleteAgentPolicy, useConfig, + sendGetAgents, + useMultipleAgentPolicies, } from '../hooks'; import { AGENTS_PREFIX } from '../../common/constants'; import type { AgentPolicy } from '../types'; -import { sendGetAgents, useMultipleAgentPolicies } from '../hooks'; interface Props { agentPolicies?: AgentPolicy[]; @@ -235,6 +236,22 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ buttonColor="danger" confirmButtonDisabled={isLoading || isLoadingAgentsCount} > + {(hasMultipleAgentPolicies || isShared) && ( + <> + + } + /> + + + )} + {isLoadingAgentsCount ? ( = ({ /> ) : agentsCount && agentPolicies ? ( <> - {(hasMultipleAgentPolicies || isShared) && ( - <> - - } - /> - - - )}