From efc567f864171b60c59c08b04642fe66ad014c60 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 21 Nov 2024 16:57:06 -0800 Subject: [PATCH] [UII] Allow to create integration policy with no agent policies (#201051) ## Summary Resolves #198165. This PR fixes an issue where `policy_ids` array was not able to be cleared behind the scenes to create an orphaned integration policy, even when cluster is able to use the multiple agent policies feature. (cherry picked from commit 3952dea3962c52dbd8d0cf01f7966670a64639d4) --- .../fleet/cypress/e2e/package_policy.cy.ts | 87 +++++++++++++------ .../fleet/cypress/screens/integrations.ts | 1 + .../steps/step_select_hosts.test.tsx | 3 +- .../components/steps/step_select_hosts.tsx | 10 +-- .../single_page_layout/hooks/form.tsx | 4 +- .../components/step_edit_hosts.test.tsx | 3 +- .../components/step_edit_hosts.tsx | 10 +-- .../services/devtools_request.tsx | 7 +- .../hooks/use_multiple_agent_policies.ts | 11 +-- 9 files changed, 80 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts b/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts index 271d2ba7b871d..1c1191e32ded1 100644 --- a/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts @@ -4,36 +4,46 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { EXISTING_HOSTS_TAB } from '../screens/fleet'; +import { + ADD_INTEGRATION_POLICY_BTN, + CREATE_PACKAGE_POLICY_SAVE_BTN, + POLICY_EDITOR, +} from '../screens/integrations'; +import { CONFIRM_MODAL } from '../screens/navigation'; import { login } from '../tasks/login'; -describe('Edit package policy', () => { - const policyConfig = { - id: 'policy-1', - name: 'fleet_server-1', - namespace: 'default', - package: { name: 'fleet_server', title: 'Fleet Server', version: '1.1.0' }, - enabled: true, - policy_id: 'fleet-server-policy', - policy_ids: ['fleet-server-policy'], - output_id: 'fleet-default-output', - inputs: [ - { - type: 'fleet-server', - policy_template: 'fleet_server', - enabled: true, - streams: [], - vars: { - host: { value: ['0.0.0.0'], type: 'text' }, - port: { value: [8220], type: 'integer' }, - max_connections: { type: 'integer' }, - custom: { value: '', type: 'yaml' }, - }, - compiled_input: { server: { port: 8220, host: '0.0.0.0' } }, - }, - ], - }; +describe('Package policy', () => { beforeEach(() => { login(); + }); + + it('should edit package policy', () => { + const policyConfig = { + id: 'policy-1', + name: 'fleet_server-1', + namespace: 'default', + package: { name: 'fleet_server', title: 'Fleet Server', version: '1.1.0' }, + enabled: true, + policy_id: 'fleet-server-policy', + policy_ids: ['fleet-server-policy'], + output_id: 'fleet-default-output', + inputs: [ + { + type: 'fleet-server', + policy_template: 'fleet_server', + enabled: true, + streams: [], + vars: { + host: { value: ['0.0.0.0'], type: 'text' }, + port: { value: [8220], type: 'integer' }, + max_connections: { type: 'integer' }, + custom: { value: '', type: 'yaml' }, + }, + compiled_input: { server: { port: 8220, host: '0.0.0.0' } }, + }, + ], + }; cy.intercept('/api/fleet/package_policies/policy-1', { item: policyConfig, @@ -111,9 +121,7 @@ describe('Edit package policy', () => { status: 'not_installed', }, }); - }); - it('should edit package policy', () => { cy.visit('/app/fleet/policies/fleet-server-policy/edit-integration/policy-1'); cy.getBySel('packagePolicyDescriptionInput').clear().type('desc'); @@ -128,4 +136,27 @@ describe('Edit package policy', () => { expect(interception.request.body.description).to.equal('desc'); }); }); + + it('should create a new orphaned package policy', () => { + cy.visit('/app/integrations/detail/system'); + cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click(); + cy.getBySel(EXISTING_HOSTS_TAB).click(); + cy.getBySel(POLICY_EDITOR.AGENT_POLICY_SELECT).should('exist'); + cy.getBySel(POLICY_EDITOR.AGENT_POLICY_CLEAR).should('not.exist'); + cy.getBySel(POLICY_EDITOR.POLICY_NAME_INPUT).clear().type('system-orphaned-test'); + + cy.intercept({ + method: 'POST', + url: '/api/fleet/package_policies', + }).as('createPackagePolicy'); + + cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).should('be.enabled').click(); + cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); + + cy.wait('@createPackagePolicy').then((interception) => { + expect(interception.request.body.name).to.equal('system-orphaned-test'); + expect(interception.request.body.policy_id).to.equal(undefined); + expect(interception.request.body.policy_ids).to.deep.equal([]); + }); + }); }); diff --git a/x-pack/plugins/fleet/cypress/screens/integrations.ts b/x-pack/plugins/fleet/cypress/screens/integrations.ts index 1a31d2dc5de31..8b29a95265ff2 100644 --- a/x-pack/plugins/fleet/cypress/screens/integrations.ts +++ b/x-pack/plugins/fleet/cypress/screens/integrations.ts @@ -39,6 +39,7 @@ export const POLICY_EDITOR = { POLICY_NAME_INPUT: 'packagePolicyNameInput', DATASET_SELECT: 'datasetComboBox', AGENT_POLICY_SELECT: 'agentPolicyMultiSelect', + AGENT_POLICY_CLEAR: 'comboBoxClearButton', INSPECT_PIPELINES_BTN: 'datastreamInspectPipelineBtn', EDIT_MAPPINGS_BTN: 'datastreamEditMappingsBtn', CREATE_MAPPINGS_BTN: 'datastreamAddCustomComponentTemplateBtn', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx index 583957861bd79..dd3945664bd6f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx @@ -123,7 +123,8 @@ describe('StepSelectHosts', () => { await waitFor(() => { expect(renderResult.getByText('New agent policy name')).toBeInTheDocument(); }); - expect(renderResult.queryByRole('tablist')).not.toBeInTheDocument(); + expect(renderResult.queryByRole('tablist')).toBeInTheDocument(); + expect(renderResult.getByText('Create agent policy')).toBeInTheDocument(); }); it('should display tabs with New hosts selected when agent policies exist', async () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx index c4504e6596353..5a9d6fd6e31f0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx @@ -105,7 +105,7 @@ export const StepSelectHosts: React.FunctionComponent = ({ const handleOnTabClick = (tab: EuiTabbedContentTab) => updateSelectedTab(tab.id as SelectedPolicyTab); - return existingAgentPolicies.length > 0 ? ( + return ( = ({ tabs={tabs} onTabClick={handleOnTabClick} /> - ) : ( - ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 1488e9c83f4d3..0f13ccaa574b1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -280,7 +280,7 @@ export function useOnSubmit({ useEffect(() => { if ( - agentPolicies.length > 0 && + (canUseMultipleAgentPolicies || agentPolicies.length > 0) && !isEqual( agentPolicies.map((policy) => policy.id), packagePolicy.policy_ids @@ -290,7 +290,7 @@ export function useOnSubmit({ policy_ids: agentPolicies.map((policy) => policy.id), }); } - }, [packagePolicy, agentPolicies, updatePackagePolicy]); + }, [packagePolicy, agentPolicies, updatePackagePolicy, canUseMultipleAgentPolicies]); const onSaveNavigate = useOnSaveNavigate({ packagePolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx index e30fa6c22c5ce..249b037a6f6c3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx @@ -124,8 +124,9 @@ describe('StepEditHosts', () => { render(); - expect(renderResult.getByText('New agent policy name')).toBeInTheDocument(); expect(renderResult.queryByRole('tablist')).not.toBeInTheDocument(); + expect(renderResult.getByText('For existing hosts:')).toBeInTheDocument(); + expect(renderResult.getByText('For a new host:')).toBeInTheDocument(); }); it('should display new policy button and existing policies when agent policies exist', () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx index 64d104172af53..54e210eddc3c1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx @@ -60,7 +60,7 @@ export const StepEditHosts: React.FunctionComponent = ({ } }, [existingAgentPolicies.length]); // eslint-disable-line react-hooks/exhaustive-deps - return existingAgentPolicies.length > 0 ? ( + return ( @@ -141,13 +141,5 @@ export const StepEditHosts: React.FunctionComponent = ({ )} - ) : ( - ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx index 8badac52213e4..aa866106e8421 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx @@ -18,6 +18,7 @@ import type { UpdatePackagePolicy, UpdateAgentPolicyRequest, } from '../../../types'; +import { canUseMultipleAgentPolicies } from '../../../hooks'; function generateKibanaDevToolsRequest(method: string, path: string, body: any) { return `${method} kbn:${path}\n${JSON.stringify(body, null, 2)}\n`; @@ -49,9 +50,13 @@ export function generateCreateAgentPolicyDevToolsRequest( export function generateCreatePackagePolicyDevToolsRequest( packagePolicy: NewPackagePolicy & { force?: boolean } ) { + const canHaveNoAgentPolicies = canUseMultipleAgentPolicies(); + return generateKibanaDevToolsRequest('POST', packagePolicyRouteService.getCreatePath(), { policy_ids: - packagePolicy.policy_ids.length > 0 ? packagePolicy.policy_ids : [''], + packagePolicy.policy_ids.length > 0 || canHaveNoAgentPolicies + ? packagePolicy.policy_ids + : [''], package: formatPackage(packagePolicy.package), ...omit(packagePolicy, 'policy_ids', 'package', 'enabled'), inputs: formatInputs(packagePolicy.inputs), diff --git a/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts b/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts index b85b944c6ee00..b2648b1d55da7 100644 --- a/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts +++ b/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts @@ -8,15 +8,16 @@ import { LICENCE_FOR_MULTIPLE_AGENT_POLICIES } from '../../common/constants'; import { ExperimentalFeaturesService } from '../services'; -import { useLicense } from './use_license'; +import { licenseService } from './use_license'; export function useMultipleAgentPolicies() { - const licenseService = useLicense(); + return { canUseMultipleAgentPolicies: canUseMultipleAgentPolicies() }; +} + +export function canUseMultipleAgentPolicies() { const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get(); const hasEnterpriseLicence = licenseService.hasAtLeast(LICENCE_FOR_MULTIPLE_AGENT_POLICIES); - const canUseMultipleAgentPolicies = enableReusableIntegrationPolicies && hasEnterpriseLicence; - - return { canUseMultipleAgentPolicies }; + return Boolean(enableReusableIntegrationPolicies && hasEnterpriseLicence); }