;
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx
index 1ede87ba34c9b8..e19ec358abd645 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx
@@ -16,7 +16,7 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{
onConfirm: () => void;
onCancel: () => void;
agentCount: number;
- agentPolicy: AgentPolicy;
+ agentPolicies: AgentPolicy[];
showUnprivilegedAgentsCallout?: boolean;
unprivilegedAgentsCount?: number;
dataStreams?: Array<{ name: string; title: string }>;
@@ -24,7 +24,7 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{
onConfirm,
onCancel,
agentCount,
- agentPolicy,
+ agentPolicies,
showUnprivilegedAgentsCallout = false,
unprivilegedAgentsCount = 0,
dataStreams,
@@ -66,11 +66,11 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{
{agentPolicy.name},
+ policyNames: {agentPolicies.map((policy) => policy.name).join(', ')},
}}
/>
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx
new file mode 100644
index 00000000000000..4b10b2e2fc9ac7
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx
@@ -0,0 +1,54 @@
+/*
+ * 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 type { EuiComboBoxOptionOption } from '@elastic/eui';
+import { EuiComboBox } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+
+import React, { useMemo } from 'react';
+
+import type { PackageInfo } from '../../../../../../../../../common';
+
+export interface Props {
+ isLoading: boolean;
+ agentPolicyMultiOptions: Array>;
+ selectedPolicyIds: string[];
+ setSelectedPolicyIds: (policyIds: string[]) => void;
+ packageInfo?: PackageInfo;
+}
+
+export const AgentPolicyMultiSelect: React.FunctionComponent = ({
+ isLoading,
+ agentPolicyMultiOptions,
+ selectedPolicyIds,
+ setSelectedPolicyIds,
+}) => {
+ const selectedOptions = useMemo(() => {
+ return agentPolicyMultiOptions.filter((option) => selectedPolicyIds.includes(option.key!));
+ }, [agentPolicyMultiOptions, selectedPolicyIds]);
+
+ return (
+ {
+ setSelectedPolicyIds(newOptions.map((option: any) => option.key));
+ }}
+ isClearable={true}
+ isLoading={isLoading}
+ />
+ );
+};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx
index 193703a2c96eed..593d25ebaa97cd 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx
@@ -12,26 +12,8 @@ import type { TestRenderer } from '../../../../../../../mock';
import { createFleetTestRendererMock } from '../../../../../../../mock';
import type { AgentPolicy, NewPackagePolicy, PackageInfo } from '../../../../../types';
-import { useGetPackagePolicies } from '../../../../../hooks';
-
import { StepDefinePackagePolicy } from './step_define_package_policy';
-jest.mock('../../../../../hooks', () => {
- return {
- ...jest.requireActual('../../../../../hooks'),
- useGetPackagePolicies: jest.fn().mockReturnValue({
- data: {
- items: [{ name: 'nginx-1' }, { name: 'other-policy' }],
- },
- isLoading: false,
- }),
- useFleetStatus: jest.fn().mockReturnValue({ isReady: true } as any),
- sendGetStatus: jest
- .fn()
- .mockResolvedValue({ data: { isReady: true, missing_requirements: [] } }),
- };
-});
-
describe('StepDefinePackagePolicy', () => {
const packageInfo: PackageInfo = {
name: 'apache',
@@ -63,18 +45,32 @@ describe('StepDefinePackagePolicy', () => {
},
],
};
- const agentPolicy: AgentPolicy = {
- id: 'agent-policy-1',
- namespace: 'ns',
- name: 'Agent policy 1',
- is_managed: false,
- status: 'active',
- updated_at: '',
- updated_by: '',
- revision: 1,
- package_policies: [],
- is_protected: false,
- };
+ const agentPolicies: AgentPolicy[] = [
+ {
+ id: 'agent-policy-1',
+ namespace: 'ns',
+ name: 'Agent policy 1',
+ is_managed: false,
+ status: 'active',
+ updated_at: '',
+ updated_by: '',
+ revision: 1,
+ package_policies: [],
+ is_protected: false,
+ },
+ {
+ id: 'agent-policy-2',
+ namespace: 'default',
+ name: 'Agent policy 2',
+ is_managed: false,
+ status: 'active',
+ updated_at: '',
+ updated_by: '',
+ revision: 1,
+ package_policies: [],
+ is_protected: false,
+ },
+ ];
let packagePolicy: NewPackagePolicy;
const mockUpdatePackagePolicy = jest.fn().mockImplementation((val: any) => {
packagePolicy = {
@@ -96,7 +92,7 @@ describe('StepDefinePackagePolicy', () => {
const render = () =>
(renderResult = testRenderer.render(
{
waitFor(() => {
expect(renderResult.getByRole('switch')).toHaveAttribute('aria-label', 'Advanced var');
+ expect(renderResult.getByTestId('packagePolicyNamespaceInput')).toHaveAttribute(
+ 'placeholder',
+ 'ns'
+ );
});
});
});
- it('should set incremented name if other package policies exist', () => {
- (useGetPackagePolicies as jest.MockedFunction).mockReturnValueOnce({
- data: {
- items: [
- { name: 'apache-1' },
- { name: 'apache-2' },
- { name: 'apache-9' },
- { name: 'apache-10' },
- ],
- },
- isLoading: false,
- });
-
- render();
-
- waitFor(() => {
- expect(renderResult.getByDisplayValue('apache-11')).toBeInTheDocument();
- });
- });
-
describe('update', () => {
describe('when package vars are introduced in a new package version', () => {
it('should display new package vars', () => {
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx
index 4f50a0c1cc5f27..add3cf72485f32 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx
@@ -48,7 +48,7 @@ const FormGroupResponsiveFields = styled(EuiDescribedFormGroup)`
`;
export const StepDefinePackagePolicy: React.FunctionComponent<{
- agentPolicy?: AgentPolicy;
+ agentPolicies?: AgentPolicy[];
packageInfo: PackageInfo;
packagePolicy: NewPackagePolicy;
updatePackagePolicy: (fields: Partial) => void;
@@ -58,7 +58,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{
noAdvancedToggle?: boolean;
}> = memo(
({
- agentPolicy,
+ agentPolicies,
packageInfo,
packagePolicy,
updatePackagePolicy,
@@ -283,8 +283,9 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{
}
>
{
return {
...jest.requireActual('../../../../../hooks'),
useGetAgentPolicies: jest.fn(),
- useGetOutputs: jest.fn().mockResolvedValue({
- data: [],
+ useGetOutputs: jest.fn().mockReturnValue({
+ data: {
+ items: [
+ {
+ id: 'logstash-1',
+ type: 'logstash',
+ },
+ ],
+ },
isLoading: false,
}),
- sendGetOneAgentPolicy: jest.fn().mockResolvedValue({
- data: { item: { id: 'policy-1' } },
- }),
+ sendBulkGetAgentPolicies: jest.fn().mockImplementation((ids) =>
+ Promise.resolve({
+ data: { items: ids.map((id: string) => ({ id, package_policies: [] })) },
+ })
+ ),
useFleetStatus: jest.fn().mockReturnValue({ isReady: true } as any),
sendGetFleetStatus: jest
.fn()
.mockResolvedValue({ data: { isReady: true, missing_requirements: [] } }),
+ useGetPackagePolicies: jest.fn().mockImplementation((query) => ({
+ data: {
+ items: query.kuery.includes('osquery_manager')
+ ? [{ policy_ids: ['policy-1'] }]
+ : query.kuery.includes('apm')
+ ? [{ policy_ids: ['policy-2'] }]
+ : [],
+ },
+ error: undefined,
+ isLoading: false,
+ resendRequest: jest.fn(),
+ })),
};
});
@@ -41,21 +66,21 @@ describe('step select agent policy', () => {
let testRenderer: TestRenderer;
let renderResult: ReturnType;
const mockSetHasAgentPolicyError = jest.fn();
- const updateAgentPolicyMock = jest.fn();
- const render = () =>
+ const updateAgentPoliciesMock = jest.fn();
+ const render = (packageInfo?: PackageInfo, selectedAgentPolicyId?: string) =>
(renderResult = testRenderer.render(
));
beforeEach(() => {
testRenderer = createFleetTestRendererMock();
- updateAgentPolicyMock.mockReset();
+ updateAgentPoliciesMock.mockReset();
});
test('should not select agent policy by default if multiple exists', async () => {
@@ -90,10 +115,151 @@ describe('step select agent policy', () => {
} as any);
render();
- await act(async () => {}); // Needed as updateAgentPolicy is called after multiple useEffect
+ await act(async () => {}); // Needed as updateAgentPolicies is called after multiple useEffect
await act(async () => {
- expect(updateAgentPolicyMock).toBeCalled();
- expect(updateAgentPolicyMock).toBeCalledWith({ id: 'policy-1' });
+ expect(updateAgentPoliciesMock).toBeCalled();
+ expect(updateAgentPoliciesMock).toBeCalledWith([{ id: 'policy-1', package_policies: [] }]);
+ });
+ });
+
+ describe('multiple agent policies', () => {
+ beforeEach(() => {
+ jest
+ .spyOn(ExperimentalFeaturesService, 'get')
+ .mockReturnValue({ enableReusableIntegrationPolicies: true });
+
+ useGetAgentPoliciesMock.mockReturnValue({
+ data: {
+ items: [
+ { id: 'policy-1', name: 'Policy 1' },
+ { id: 'policy-2', name: 'Policy 2' },
+ ],
+ },
+ error: undefined,
+ isLoading: false,
+ resendRequest: jest.fn(),
+ } as any);
+ });
+
+ test('should select agent policy by default if one exists', async () => {
+ useGetAgentPoliciesMock.mockReturnValueOnce({
+ data: { items: [{ id: 'policy-1', name: 'Policy 1' }] },
+ error: undefined,
+ isLoading: false,
+ resendRequest: jest.fn(),
+ } as any);
+
+ render();
+ await act(async () => {}); // Needed as updateAgentPolicies is called after multiple useEffect
+ await act(async () => {
+ expect(updateAgentPoliciesMock).toBeCalledWith([{ id: 'policy-1', package_policies: [] }]);
+ });
+ });
+
+ test('should not select agent policy by default if multiple exists', async () => {
+ useGetAgentPoliciesMock.mockReturnValue({
+ data: {
+ items: [
+ { id: 'policy-1', name: 'Policy 1' },
+ { id: 'policy-2', name: 'Policy 2' },
+ ],
+ },
+ error: undefined,
+ isLoading: false,
+ resendRequest: jest.fn(),
+ } as any);
+
+ render();
+
+ await act(async () => {
+ const select = renderResult.container.querySelector(
+ '[data-test-subj="agentPolicyMultiSelect"]'
+ );
+ expect((select as any)?.value).toEqual(undefined);
+
+ expect(renderResult.getByText('An agent policy is required.')).toBeVisible();
+ });
+ });
+
+ test('should select agent policy if pre selected', async () => {
+ render(undefined, 'policy-1');
+ await act(async () => {}); // Needed as updateAgentPolicies is called after multiple useEffect
+ await act(async () => {
+ expect(updateAgentPoliciesMock).toBeCalledWith([{ id: 'policy-1', package_policies: [] }]);
+ });
+ });
+
+ test('should select multiple agent policies', async () => {
+ const result = render();
+ expect(result.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument();
+ await act(async () => {
+ result.getByTestId('comboBoxToggleListButton').click();
+ });
+ expect(result.getAllByTestId('agentPolicyMultiItem').length).toBe(2);
+ await act(async () => {
+ result.getByText('Policy 1').click();
+ });
+ await act(async () => {
+ result.getByText('Policy 2').click();
+ });
+ expect(updateAgentPoliciesMock).toBeCalledWith([
+ { id: 'policy-1', package_policies: [] },
+ { id: 'policy-2', package_policies: [] },
+ ]);
+ });
+
+ test('should disable option if agent policy has limited package', async () => {
+ useGetAgentPoliciesMock.mockReturnValue({
+ data: {
+ items: [
+ { id: 'policy-1', name: 'Policy 1' },
+ { id: 'policy-2', name: 'Policy 2' },
+ { id: 'policy-3', name: 'Policy 3' },
+ ],
+ },
+ error: undefined,
+ isLoading: false,
+ resendRequest: jest.fn(),
+ } as any);
+ const result = render({
+ name: 'osquery_manager',
+ policy_templates: [{ multiple: false }],
+ } as any);
+ expect(result.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument();
+ await act(async () => {
+ result.getByTestId('comboBoxToggleListButton').click();
+ });
+ expect(
+ result.getByText('Policy 1').closest('[data-test-subj="agentPolicyMultiItem"]')
+ ).toBeDisabled();
+ });
+
+ test('should disable option if agent policy has apm package and logstash output', async () => {
+ useGetAgentPoliciesMock.mockReturnValue({
+ data: {
+ items: [
+ { id: 'policy-1', name: 'Policy 1' },
+ { id: 'policy-2', name: 'Policy 2', data_output_id: 'logstash-1' },
+ { id: 'policy-3', name: 'Policy 3' },
+ ],
+ },
+ error: undefined,
+ isLoading: false,
+ resendRequest: jest.fn(),
+ } as any);
+ const result = render({
+ name: 'apm',
+ } as any);
+ expect(result.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument();
+ await act(async () => {
+ result.getByTestId('comboBoxToggleListButton').click();
+ });
+ expect(
+ result.getByText('Policy 2').closest('[data-test-subj="agentPolicyMultiItem"]')
+ ).toBeDisabled();
+ expect(
+ result.getByTitle('Policy 2').querySelector('[data-euiicon-type="warningFilled"]')
+ ).toBeInTheDocument();
});
});
});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx
index 1524c3590c82b8..262efea5e32fbf 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx
@@ -9,7 +9,8 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import type { EuiSuperSelectOption } from '@elastic/eui';
+import type { EuiComboBoxOptionOption, EuiSuperSelectOption } from '@elastic/eui';
+import { EuiIcon, EuiToolTip } from '@elastic/eui';
import { EuiSuperSelect } from '@elastic/eui';
import {
EuiFlexGroup,
@@ -23,13 +24,17 @@ import {
import { Error } from '../../../../../components';
import type { AgentPolicy, Output, PackageInfo } from '../../../../../types';
-import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../../../../services';
+import {
+ isPackageLimited,
+ doesAgentPolicyAlreadyIncludePackage,
+ ExperimentalFeaturesService,
+} from '../../../../../services';
import {
useGetAgentPolicies,
useGetOutputs,
- sendGetOneAgentPolicy,
useFleetStatus,
useGetPackagePolicies,
+ sendBulkGetAgentPolicies,
} from '../../../../../hooks';
import {
FLEET_APM_PACKAGE,
@@ -38,6 +43,8 @@ import {
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
} from '../../../../../../../../common/constants';
+import { AgentPolicyMultiSelect } from './components/agent_policy_multi_select';
+
const AgentPolicyFormRow = styled(EuiFormRow)`
.euiFormRow__label {
width: 100%;
@@ -126,7 +133,7 @@ function useAgentPoliciesOptions(packageInfo?: PackageInfo) {
>
@@ -147,11 +154,55 @@ function useAgentPoliciesOptions(packageInfo?: PackageInfo) {
]
);
+ const agentPolicyMultiOptions: Array> = useMemo(
+ () =>
+ packageInfo && !isOutputLoading && !isAgentPoliciesLoading && !isLoadingPackagePolicies
+ ? agentPolicies.map((policy) => {
+ const isLimitedPackageAlreadyInPolicy =
+ isPackageLimited(packageInfo) &&
+ packagePoliciesForThisPackageByAgentPolicyId?.[policy.id];
+
+ const isAPMPackageAndDataOutputIsLogstash =
+ packageInfo?.name === FLEET_APM_PACKAGE &&
+ getDataOutputForPolicy(policy)?.type === outputType.Logstash;
+
+ return {
+ append: isAPMPackageAndDataOutputIsLogstash ? (
+
+ }
+ >
+
+
+ ) : null,
+ key: policy.id,
+ label: policy.name,
+ disabled: isLimitedPackageAlreadyInPolicy || isAPMPackageAndDataOutputIsLogstash,
+ 'data-test-subj': 'agentPolicyMultiItem',
+ };
+ })
+ : [],
+ [
+ packageInfo,
+ agentPolicies,
+ packagePoliciesForThisPackageByAgentPolicyId,
+ getDataOutputForPolicy,
+ isOutputLoading,
+ isAgentPoliciesLoading,
+ isLoadingPackagePolicies,
+ ]
+ );
+
return {
agentPoliciesError,
isLoading: isOutputLoading || isAgentPoliciesLoading || isLoadingPackagePolicies,
agentPolicyOptions,
agentPolicies,
+ agentPolicyMultiOptions,
};
}
@@ -163,14 +214,14 @@ function doesAgentPolicyHaveLimitedPackage(policy: AgentPolicy, pkgInfo: Package
export const StepSelectAgentPolicy: React.FunctionComponent<{
packageInfo?: PackageInfo;
- agentPolicy: AgentPolicy | undefined;
- updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void;
+ agentPolicies: AgentPolicy[];
+ updateAgentPolicies: (agentPolicies: AgentPolicy[]) => void;
setHasAgentPolicyError: (hasError: boolean) => void;
selectedAgentPolicyId?: string;
}> = ({
packageInfo,
- agentPolicy,
- updateAgentPolicy: updateSelectedAgentPolicy,
+ agentPolicies,
+ updateAgentPolicies: updateSelectedAgentPolicies,
setHasAgentPolicyError,
selectedAgentPolicyId,
}) => {
@@ -178,69 +229,101 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState();
- const { isLoading, agentPoliciesError, agentPolicyOptions, agentPolicies } =
- useAgentPoliciesOptions(packageInfo);
- // Selected agent policy state
- const [selectedPolicyId, setSelectedPolicyId] = useState(
- agentPolicy?.id ??
- (selectedAgentPolicyId || (agentPolicies.length === 1 ? agentPolicies[0].id : undefined))
- );
+ const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get();
+
+ const {
+ isLoading,
+ agentPoliciesError,
+ agentPolicyOptions,
+ agentPolicyMultiOptions,
+ agentPolicies: existingAgentPolicies,
+ } = useAgentPoliciesOptions(packageInfo);
+
+ const [selectedPolicyIds, setSelectedPolicyIds] = useState([]);
const [isLoadingSelectedAgentPolicy, setIsLoadingSelectedAgentPolicy] = useState(false);
- const [selectedAgentPolicy, setSelectedAgentPolicy] = useState(
- agentPolicy
- );
+ const [selectedAgentPolicies, setSelectedAgentPolicies] = useState(agentPolicies);
- const updateAgentPolicy = useCallback(
- (selectedPolicy: AgentPolicy | undefined) => {
- setSelectedAgentPolicy(selectedPolicy);
- updateSelectedAgentPolicy(selectedPolicy);
+ const updateAgentPolicies = useCallback(
+ (selectedPolicies: AgentPolicy[]) => {
+ setSelectedAgentPolicies(selectedPolicies);
+ updateSelectedAgentPolicies(selectedPolicies);
},
- [updateSelectedAgentPolicy]
+ [updateSelectedAgentPolicies]
);
// Update parent selected agent policy state
useEffect(() => {
const fetchAgentPolicyInfo = async () => {
- if (selectedPolicyId) {
+ if (selectedPolicyIds.length > 0) {
setIsLoadingSelectedAgentPolicy(true);
- const { data, error } = await sendGetOneAgentPolicy(selectedPolicyId);
+ const { data, error } = await sendBulkGetAgentPolicies(selectedPolicyIds, { full: true });
if (error) {
setSelectedAgentPolicyError(error);
- updateAgentPolicy(undefined);
- } else if (data && data.item) {
+ updateAgentPolicies([]);
+ } else if (data && data.items) {
setSelectedAgentPolicyError(undefined);
- updateAgentPolicy(data.item);
+ updateAgentPolicies(data.items);
}
setIsLoadingSelectedAgentPolicy(false);
} else {
setSelectedAgentPolicyError(undefined);
- updateAgentPolicy(undefined);
+ updateAgentPolicies([]);
}
};
- if (!agentPolicy || selectedPolicyId !== agentPolicy.id) {
+ const agentPoliciesHaveAllSelectedIds = selectedPolicyIds.every((id) =>
+ agentPolicies.map((policy) => policy.id).includes(id)
+ );
+ if (agentPolicies.length === 0 || !agentPoliciesHaveAllSelectedIds) {
fetchAgentPolicyInfo();
+ } else if (agentPoliciesHaveAllSelectedIds && selectedPolicyIds.length < agentPolicies.length) {
+ setSelectedAgentPolicyError(undefined);
+ updateAgentPolicies(agentPolicies.filter((policy) => selectedPolicyIds.includes(policy.id)));
}
- }, [selectedPolicyId, agentPolicy, updateAgentPolicy]);
+ }, [selectedPolicyIds, agentPolicies, updateAgentPolicies]);
// Try to select default agent policy
useEffect(() => {
- if (!selectedPolicyId && agentPolicies.length && agentPolicyOptions.length) {
- const enabledOptions = agentPolicyOptions.filter((option) => !option.disabled);
- if (enabledOptions.length === 1) {
- setSelectedPolicyId(enabledOptions[0].value as string | undefined);
+ if (
+ selectedPolicyIds.length === 0 &&
+ existingAgentPolicies.length &&
+ (enableReusableIntegrationPolicies
+ ? agentPolicyMultiOptions.length
+ : agentPolicyOptions.length)
+ ) {
+ if (enableReusableIntegrationPolicies) {
+ const enabledOptions = agentPolicyMultiOptions.filter((option) => !option.disabled);
+ if (enabledOptions.length === 1) {
+ setSelectedPolicyIds([enabledOptions[0].key!]);
+ } else if (selectedAgentPolicyId) {
+ setSelectedPolicyIds([selectedAgentPolicyId]);
+ }
+ } else {
+ const enabledOptions = agentPolicyOptions.filter((option) => !option.disabled);
+ if (enabledOptions.length === 1) {
+ setSelectedPolicyIds([enabledOptions[0].value]);
+ } else if (selectedAgentPolicyId) {
+ setSelectedPolicyIds([selectedAgentPolicyId]);
+ }
}
}
- }, [agentPolicies, agentPolicyOptions, selectedPolicyId]);
+ }, [
+ agentPolicyOptions,
+ agentPolicyMultiOptions,
+ enableReusableIntegrationPolicies,
+ selectedAgentPolicyId,
+ selectedPolicyIds,
+ existingAgentPolicies,
+ ]);
// Bubble up any issues with agent policy selection
useEffect(() => {
- if (selectedPolicyId && !selectedAgentPolicyError) {
+ if (selectedPolicyIds.length > 0 && !selectedAgentPolicyError) {
setHasAgentPolicyError(false);
} else setHasAgentPolicyError(true);
- }, [selectedAgentPolicyError, selectedPolicyId, setHasAgentPolicyError]);
+ }, [selectedAgentPolicyError, selectedPolicyIds, setHasAgentPolicyError]);
const onChange = useCallback(
- (newValue: string) => setSelectedPolicyId(newValue === '' ? undefined : newValue),
+ (newValue: string) => setSelectedPolicyIds(newValue === '' ? [] : [newValue]),
[]
);
@@ -298,24 +381,28 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
}
helpText={
- isFleetReady && selectedPolicyId && !isLoadingSelectedAgentPolicy ? (
+ isFleetReady && selectedPolicyIds.length > 0 && !isLoadingSelectedAgentPolicy ? (
acc + (curr.agents ?? 0),
+ 0
+ ),
}}
/>
) : null
}
isInvalid={Boolean(
- !selectedPolicyId ||
+ selectedPolicyIds.length === 0 ||
!packageInfo ||
- (selectedAgentPolicy &&
- doesAgentPolicyHaveLimitedPackage(selectedAgentPolicy, packageInfo))
+ selectedAgentPolicies.every((selectedAgentPolicy) =>
+ doesAgentPolicyHaveLimitedPackage(selectedAgentPolicy, packageInfo)
+ )
)}
error={
- !selectedPolicyId ? (
+ selectedPolicyIds.length === 0 ? (
-
+ {enableReusableIntegrationPolicies ? (
+
+ ) : (
+
+ )}
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 89de7ba44b3e60..70fb22fcebdf1c 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
@@ -24,9 +24,11 @@ jest.mock('../../../../../hooks', () => {
data: [],
isLoading: false,
}),
- sendGetOneAgentPolicy: jest.fn().mockResolvedValue({
- data: { item: { id: 'policy-1', name: 'Agent policy 1' } },
- }),
+ sendGetOneAgentPolicy: jest.fn().mockImplementation((id) =>
+ Promise.resolve({
+ data: { item: { id, name: `Agent policy ${id}` } },
+ })
+ ),
};
});
@@ -44,18 +46,32 @@ describe('StepSelectHosts', () => {
status: 'not_installed',
vars: [],
};
- const agentPolicy: AgentPolicy = {
- id: 'agent-policy-1',
- namespace: 'default',
- name: 'Agent policy 1',
- is_managed: false,
- status: 'active',
- updated_at: '',
- updated_by: '',
- revision: 1,
- package_policies: [],
- is_protected: false,
- };
+ const agentPolicies: AgentPolicy[] = [
+ {
+ id: '1',
+ namespace: 'default',
+ name: 'Agent policy 1',
+ is_managed: false,
+ status: 'active',
+ updated_at: '',
+ updated_by: '',
+ revision: 1,
+ package_policies: [],
+ is_protected: false,
+ },
+ {
+ id: '2',
+ namespace: 'default',
+ name: 'Agent policy 2',
+ is_managed: false,
+ status: 'active',
+ updated_at: '',
+ updated_by: '',
+ revision: 1,
+ package_policies: [],
+ is_protected: false,
+ },
+ ];
const newAgentPolicy = {
name: '',
namespace: 'default',
@@ -67,8 +83,8 @@ describe('StepSelectHosts', () => {
const render = () =>
(renderResult = testRenderer.render(
{
it('should display tabs with New hosts selected when agent policies exist', () => {
(useGetAgentPolicies as jest.MockedFunction).mockReturnValue({
data: {
- items: [{ id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' }],
+ items: [{ id: '1', name: 'Agent policy 1', namespace: 'default' }],
},
});
@@ -110,7 +126,7 @@ describe('StepSelectHosts', () => {
waitFor(() => {
expect(renderResult.getByRole('tablist')).toBeInTheDocument();
- expect(renderResult.getByText('Agent policy 2')).toBeInTheDocument();
+ expect(renderResult.getByText('Agent policy 3')).toBeInTheDocument();
});
expect(renderResult.getByText('New hosts').closest('button')).toHaveAttribute(
'aria-selected',
@@ -121,7 +137,7 @@ describe('StepSelectHosts', () => {
it('should display dropdown with agent policy selected when Existing hosts selected', async () => {
(useGetAgentPolicies as jest.MockedFunction).mockReturnValue({
data: {
- items: [{ id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' }],
+ items: [{ id: '1', name: 'Agent policy 1', namespace: 'default' }],
},
});
@@ -143,8 +159,8 @@ describe('StepSelectHosts', () => {
(useGetAgentPolicies as jest.MockedFunction).mockReturnValue({
data: {
items: [
- { id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' },
- { id: 'agent-policy-2', name: 'Agent policy 2', namespace: 'default' },
+ { id: '1', name: 'Agent policy 1', namespace: 'default' },
+ { id: '2', name: 'Agent policy 2', namespace: 'default' },
],
},
});
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 87ef9ac864bfdb..a4b4f5a3e0970e 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
@@ -32,8 +32,8 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)`
`;
interface Props {
- agentPolicy: AgentPolicy | undefined;
- updateAgentPolicy: (u: AgentPolicy | undefined) => void;
+ agentPolicies: AgentPolicy[];
+ updateAgentPolicies: (u: AgentPolicy[]) => void;
newAgentPolicy: Partial;
updateNewAgentPolicy: (u: Partial) => void;
withSysMonitoring: boolean;
@@ -46,8 +46,8 @@ interface Props {
}
export const StepSelectHosts: React.FunctionComponent = ({
- agentPolicy,
- updateAgentPolicy,
+ agentPolicies,
+ updateAgentPolicies,
newAgentPolicy,
updateNewAgentPolicy,
withSysMonitoring,
@@ -58,7 +58,7 @@ export const StepSelectHosts: React.FunctionComponent = ({
updateSelectedTab,
selectedAgentPolicyId,
}) => {
- let agentPolicies: AgentPolicy[] = [];
+ let existingAgentPolicies: AgentPolicy[] = [];
const { data: agentPoliciesData, error: err } = useGetAgentPolicies({
page: 1,
perPage: SO_SEARCH_LIMIT,
@@ -71,19 +71,19 @@ export const StepSelectHosts: React.FunctionComponent = ({
// eslint-disable-next-line no-console
console.debug('Could not retrieve agent policies');
}
- agentPolicies = useMemo(
+ existingAgentPolicies = useMemo(
() => agentPoliciesData?.items.filter((policy) => !policy.is_managed) || [],
[agentPoliciesData?.items]
);
useEffect(() => {
- if (agentPolicies.length > 0) {
+ if (existingAgentPolicies.length > 0) {
updateNewAgentPolicy({
...newAgentPolicy,
- name: incrementPolicyName(agentPolicies),
+ name: incrementPolicyName(existingAgentPolicies),
});
}
- }, [agentPolicies.length]); // eslint-disable-line react-hooks/exhaustive-deps
+ }, [existingAgentPolicies.length]); // eslint-disable-line react-hooks/exhaustive-deps
const tabs = [
{
@@ -107,8 +107,8 @@ export const StepSelectHosts: React.FunctionComponent = ({
content: (
@@ -119,7 +119,7 @@ export const StepSelectHosts: React.FunctionComponent = ({
const handleOnTabClick = (tab: EuiTabbedContentTab) =>
updateSelectedTab(tab.id as SelectedPolicyTab);
- return agentPolicies.length > 0 ? (
+ return existingAgentPolicies.length > 0 ? (
{
expect(renderResult.result.current.packagePolicy).toEqual({
id: 'new-id',
- policy_id: '',
- policy_ids: [''],
+ policy_ids: [],
namespace: 'newspace',
description: '',
enabled: true,
@@ -147,8 +146,7 @@ describe('useOnSubmit', () => {
inputs: [],
name: 'apache-1',
namespace: '',
- policy_id: '',
- policy_ids: [''],
+ policy_ids: [],
package: {
name: 'apache',
title: 'Apache',
@@ -193,8 +191,7 @@ describe('useOnSubmit', () => {
inputs: [],
name: 'apache-11',
namespace: '',
- policy_id: '',
- policy_ids: [''],
+ policy_ids: [],
package: {
name: 'apache',
title: 'Apache',
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 2f37ffea1b2149..be80a966972691 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
@@ -9,6 +9,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { safeLoad } from 'js-yaml';
+import { isEqual } from 'lodash';
+
import type {
AgentPolicy,
NewPackagePolicy,
@@ -121,7 +123,7 @@ export function useOnSubmit({
// Used to initialize the package policy once
const isInitializedRef = useRef(false);
- const [agentPolicy, setAgentPolicy] = useState();
+ const [agentPolicies, setAgentPolicies] = useState([]);
// New package policy state
const [packagePolicy, setPackagePolicy] = useState({
...DEFAULT_PACKAGE_POLICY,
@@ -136,22 +138,25 @@ export function useOnSubmit({
useAgentless();
// Update agent policy method
- const updateAgentPolicy = useCallback(
- (updatedAgentPolicy: AgentPolicy | undefined) => {
- if (updatedAgentPolicy) {
- setAgentPolicy(updatedAgentPolicy);
+ const updateAgentPolicies = useCallback(
+ (updatedAgentPolicies: AgentPolicy[]) => {
+ if (isEqual(updatedAgentPolicies, agentPolicies)) {
+ return;
+ }
+ if (updatedAgentPolicies.length > 0) {
+ setAgentPolicies(updatedAgentPolicies);
if (packageInfo) {
setHasAgentPolicyError(false);
}
} else {
setHasAgentPolicyError(true);
- setAgentPolicy(undefined);
+ setAgentPolicies([]);
}
// eslint-disable-next-line no-console
- console.debug('Agent policy updated', updatedAgentPolicy);
+ console.debug('Agent policy updated', updatedAgentPolicies);
},
- [packageInfo, setAgentPolicy]
+ [packageInfo, agentPolicies]
);
// Update package policy validation
const updatePackagePolicyValidation = useCallback(
@@ -221,7 +226,7 @@ export function useOnSubmit({
updatePackagePolicy(
packageToPackagePolicy(
packageInfo,
- agentPolicy?.id || '',
+ agentPolicies.map((policy) => policy.id),
'',
DEFAULT_PACKAGE_POLICY.name || incrementedName,
DEFAULT_PACKAGE_POLICY.description,
@@ -231,15 +236,21 @@ export function useOnSubmit({
setIsInitialized(true);
}
init();
- }, [packageInfo, agentPolicy, updatePackagePolicy, integrationToEnable, isInitialized]);
+ }, [packageInfo, agentPolicies, updatePackagePolicy, integrationToEnable, isInitialized]);
useEffect(() => {
- if (agentPolicy && !packagePolicy.policy_ids.includes(agentPolicy.id)) {
+ if (
+ agentPolicies.length > 0 &&
+ !isEqual(
+ agentPolicies.map((policy) => policy.id),
+ packagePolicy.policy_ids
+ )
+ ) {
updatePackagePolicy({
- policy_ids: [agentPolicy.id],
+ policy_ids: agentPolicies.map((policy) => policy.id),
});
}
- }, [packagePolicy, agentPolicy, updatePackagePolicy]);
+ }, [packagePolicy, agentPolicies, updatePackagePolicy]);
const onSaveNavigate = useOnSaveNavigate({
packagePolicy,
@@ -295,7 +306,7 @@ export function useOnSubmit({
packagePolicy,
withSysMonitoring,
});
- setAgentPolicy(createdPolicy);
+ setAgentPolicies([createdPolicy]);
updatePackagePolicy({ policy_ids: [createdPolicy.id] });
} catch (e) {
setFormState('VALID');
@@ -361,7 +372,7 @@ export function useOnSubmit({
setSavedPackagePolicy(data!.item);
const promptForAgentEnrollment =
- !(agentCount && agentPolicy) && hasFleetAddAgentsPrivileges;
+ !(agentCount && agentPolicies.length > 0) && hasFleetAddAgentsPrivileges;
if (promptForAgentEnrollment && hasAzureArmTemplate) {
setFormState('SUBMITTED_AZURE_ARM_TEMPLATE');
return;
@@ -389,9 +400,9 @@ export function useOnSubmit({
}),
text: promptForAgentEnrollment
? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
- defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyName}'' policy.`,
+ defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyNames}'' policies.`,
values: {
- agentPolicyName: agentPolicy!.name,
+ agentPolicyNames: agentPolicies.map((policy) => policy.name).join(', '),
},
})
: undefined,
@@ -431,15 +442,15 @@ export function useOnSubmit({
newAgentPolicy,
updatePackagePolicy,
notifications.toasts,
- agentPolicy,
+ agentPolicies,
onSaveNavigate,
confirmForceInstall,
]
);
return {
- agentPolicy,
- updateAgentPolicy,
+ agentPolicies,
+ updateAgentPolicies,
packagePolicy,
updatePackagePolicy,
savedPackagePolicy,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts
index dd2a1dcc81c79b..5099cdde2277c5 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts
@@ -25,7 +25,7 @@ type MockFn = jest.MockedFunction;
describe('useSetupTechnology', () => {
const updateNewAgentPolicyMock = jest.fn();
- const updateAgentPolicyMock = jest.fn();
+ const updateAgentPoliciesMock = jest.fn();
const setSelectedPolicyTabMock = jest.fn();
const newAgentPolicyMock = {
name: 'mock_new_agent_policy',
@@ -59,7 +59,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
@@ -74,7 +74,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
@@ -95,7 +95,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
@@ -110,7 +110,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
@@ -121,7 +121,7 @@ describe('useSetupTechnology', () => {
result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS);
});
- expect(updateAgentPolicyMock).toHaveBeenCalledWith({ id: 'agentless-policy-id' });
+ expect(updateAgentPoliciesMock).toHaveBeenCalledWith([{ id: 'agentless-policy-id' }]);
expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.EXISTING);
});
@@ -130,7 +130,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
@@ -164,7 +164,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
@@ -183,7 +183,7 @@ describe('useSetupTechnology', () => {
useSetupTechnology({
updateNewAgentPolicy: updateNewAgentPolicyMock,
newAgentPolicy: newAgentPolicyMock,
- updateAgentPolicy: updateAgentPolicyMock,
+ updateAgentPolicies: updateAgentPoliciesMock,
setSelectedPolicyTab: setSelectedPolicyTabMock,
})
);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts
index ee2ef29f71d12f..cc67adddeca191 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts
@@ -64,13 +64,13 @@ export const useAgentless = () => {
export function useSetupTechnology({
updateNewAgentPolicy,
newAgentPolicy,
- updateAgentPolicy,
+ updateAgentPolicies,
setSelectedPolicyTab,
packageInfo,
}: {
updateNewAgentPolicy: (policy: NewAgentPolicy) => void;
newAgentPolicy: NewAgentPolicy;
- updateAgentPolicy: (policy: AgentPolicy | undefined) => void;
+ updateAgentPolicies: (policies: AgentPolicy[]) => void;
setSelectedPolicyTab: (tab: SelectedPolicyTab) => void;
packageInfo?: PackageInfo;
}) {
@@ -109,13 +109,13 @@ export function useSetupTechnology({
if (setupTechnology === SetupTechnology.AGENTLESS) {
if (agentlessPolicy) {
- updateAgentPolicy(agentlessPolicy);
+ updateAgentPolicies([agentlessPolicy]);
setSelectedPolicyTab(SelectedPolicyTab.EXISTING);
}
} else if (setupTechnology === SetupTechnology.AGENT_BASED) {
updateNewAgentPolicy(newAgentPolicy);
setSelectedPolicyTab(SelectedPolicyTab.NEW);
- updateAgentPolicy(undefined);
+ updateAgentPolicies([]);
}
setSelectedSetupTechnology(setupTechnology);
},
@@ -123,7 +123,7 @@ export function useSetupTechnology({
isAgentlessEnabled,
selectedSetupTechnology,
agentlessPolicy,
- updateAgentPolicy,
+ updateAgentPolicies,
setSelectedPolicyTab,
updateNewAgentPolicy,
newAgentPolicy,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx
index 43c2c4b4e28418..f79a5ad00c67a6 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx
@@ -60,6 +60,11 @@ jest.mock('../../../../hooks', () => {
},
},
}),
+ sendBulkGetAgentPolicies: jest.fn().mockImplementation((ids) =>
+ Promise.resolve({
+ data: { items: ids.map((id: string) => ({ id, package_policies: [] })) },
+ })
+ ),
useGetPackageInfoByKeyQuery: jest.fn(),
sendGetSettings: jest.fn().mockResolvedValue({
data: { item: {} },
@@ -308,7 +313,6 @@ describe('When on the package policy create page', () => {
title: 'Nginx',
version: '1.3.0',
},
- policy_id: '',
policy_ids: ['agent-policy-1'],
vars: undefined,
};
@@ -371,6 +375,7 @@ describe('When on the package policy create page', () => {
expect(sendCreatePackagePolicy as jest.MockedFunction).toHaveBeenCalledWith({
...newPackagePolicy,
policy_id: 'agent-policy-1',
+ policy_ids: ['agent-policy-1'],
force: false,
});
expect(sendCreateAgentPolicy as jest.MockedFunction).not.toHaveBeenCalled();
@@ -498,6 +503,9 @@ describe('When on the package policy create page', () => {
(sendCreateAgentPolicy as jest.MockedFunction).mockClear();
(sendCreatePackagePolicy as jest.MockedFunction).mockClear();
+ (sendGetAgentStatus as jest.MockedFunction).mockResolvedValue({
+ data: { results: { total: 0 } },
+ });
});
test('should create agent policy before creating package policy on submit when new hosts is selected', async () => {
@@ -545,7 +553,7 @@ describe('When on the package policy create page', () => {
});
test('should show modal if agent policy has agents', async () => {
- (sendGetAgentStatus as jest.MockedFunction).mockResolvedValueOnce({
+ (sendGetAgentStatus as jest.MockedFunction).mockResolvedValue({
data: { results: { total: 1 } },
});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index 4bc608e4d90592..f2646152225621 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -155,8 +155,8 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
onSubmit,
updatePackagePolicy,
packagePolicy,
- agentPolicy,
- updateAgentPolicy,
+ agentPolicies,
+ updateAgentPolicies,
savedPackagePolicy,
formState,
setFormState,
@@ -216,19 +216,23 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
);
// Retrieve agent count
- const agentPolicyId = agentPolicy?.id;
+ const agentPolicyIds = agentPolicies.map((policy) => policy.id);
const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
from,
pkgkey: params.pkgkey,
- agentPolicyId,
+ agentPolicyId: agentPolicyIds[0],
});
useEffect(() => {
const getAgentCount = async () => {
- const { data } = await sendGetAgentStatus({ policyId: agentPolicyId });
- if (data?.results.total !== undefined) {
- setAgentCount(data.results.total);
+ let count = 0;
+ for (const policyId of agentPolicyIds) {
+ const { data } = await sendGetAgentStatus({ policyId });
+ if (data?.results.total) {
+ count += data.results.total;
+ }
}
+ setAgentCount(count);
};
if (selectedPolicyTab === SelectedPolicyTab.NEW) {
@@ -236,10 +240,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
return;
}
- if (isFleetEnabled && agentPolicyId) {
+ if (isFleetEnabled && agentPolicyIds.length > 0) {
getAgentCount();
}
- }, [agentPolicyId, selectedPolicyTab, isFleetEnabled]);
+ }, [agentPolicyIds, selectedPolicyTab, isFleetEnabled]);
const handleExtensionViewOnChange = useCallback<
PackagePolicyEditExtensionComponentProps['onChange']
@@ -269,18 +273,18 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
from,
cancelUrl,
onCancel: cancelClickHandler,
- agentPolicy,
+ agentPolicies,
packageInfo,
integrationInfo,
}),
- [agentPolicy, cancelClickHandler, cancelUrl, from, integrationInfo, packageInfo]
+ [agentPolicies, cancelClickHandler, cancelUrl, from, integrationInfo, packageInfo]
);
const stepSelectAgentPolicy = useMemo(
() => (
acc + (curr.unprivileged_agents ?? 0),
+ 0
+ );
return (
- {formState === 'CONFIRM' && agentPolicy && (
+ {formState === 'CONFIRM' && agentPolicies.length > 0 && (
setFormState('VALID')}
showUnprivilegedAgentsCallout={Boolean(
- packageInfo &&
- isRootPrivilegesRequired(packageInfo) &&
- (agentPolicy?.unprivileged_agents ?? 0) > 0
+ packageInfo && isRootPrivilegesRequired(packageInfo) && unprivilegedAgentsCount > 0
)}
- unprivilegedAgentsCount={agentPolicy?.unprivileged_agents ?? 0}
+ unprivilegedAgentsCount={unprivilegedAgentsCount}
dataStreams={rootPrivilegedDataStreams}
/>
)}
{formState === 'SUBMITTED_NO_AGENTS' &&
- agentPolicy &&
+ agentPolicies.length > 0 &&
packageInfo &&
savedPackagePolicy && (
navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
- {formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' && agentPolicy && savedPackagePolicy && (
- navigateAddAgent(savedPackagePolicy)}
- onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
- />
- )}
- {formState === 'SUBMITTED_CLOUD_FORMATION' && agentPolicy && savedPackagePolicy && (
- navigateAddAgent(savedPackagePolicy)}
- onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
- />
- )}
- {formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' && agentPolicy && savedPackagePolicy && (
- navigateAddAgent(savedPackagePolicy)}
- onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
- />
- )}
+ {formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' &&
+ agentPolicies.length > 0 &&
+ savedPackagePolicy && (
+ navigateAddAgent(savedPackagePolicy)}
+ onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
+ />
+ )}
+ {formState === 'SUBMITTED_CLOUD_FORMATION' &&
+ agentPolicies.length > 0 &&
+ savedPackagePolicy && (
+ navigateAddAgent(savedPackagePolicy)}
+ onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
+ />
+ )}
+ {formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' &&
+ agentPolicies.length > 0 &&
+ savedPackagePolicy && (
+ navigateAddAgent(savedPackagePolicy)}
+ onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
+ />
+ )}
{packageInfo && (
(
{agentCount ? (
{
setAgentCount(0);
submitUpdateAgentPolicy();
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx
index a44d218130d4f9..fe7d70d6976a93 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx
@@ -81,7 +81,7 @@ export function usePackagePolicyWithRelatedData(
});
const [originalPackagePolicy, setOriginalPackagePolicy] =
useState();
- const [agentPolicy, setAgentPolicy] = useState();
+ const [agentPolicies, setAgentPolicies] = useState([]);
const [isLoadingData, setIsLoadingData] = useState(true);
const [dryRunData, setDryRunData] = useState();
const [loadingError, setLoadingError] = useState();
@@ -171,17 +171,21 @@ export function usePackagePolicyWithRelatedData(
throw packagePolicyError;
}
- const { data: agentPolicyData, error: agentPolicyError } = await sendGetOneAgentPolicy(
- packagePolicyData!.item.policy_ids[0] // TODO multiple
- );
+ const newAgentPolicies = [];
+ for (const policyId of packagePolicyData!.item.policy_ids) {
+ const { data: agentPolicyData, error: agentPolicyError } = await sendGetOneAgentPolicy(
+ policyId
+ );
- if (agentPolicyError) {
- throw agentPolicyError;
- }
+ if (agentPolicyError) {
+ throw agentPolicyError;
+ }
- if (agentPolicyData?.item) {
- setAgentPolicy(agentPolicyData.item);
+ if (agentPolicyData?.item) {
+ newAgentPolicies.push(agentPolicyData.item);
+ }
}
+ setAgentPolicies(newAgentPolicies);
const { data: upgradePackagePolicyDryRunData, error: upgradePackagePolicyDryRunError } =
await sendUpgradePackagePolicyDryRun([packagePolicyId]);
@@ -353,7 +357,7 @@ export function usePackagePolicyWithRelatedData(
isUpgrade,
savePackagePolicy,
isLoadingData,
- agentPolicy,
+ agentPolicies,
loadingError,
packagePolicy,
originalPackagePolicy,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
index 475822e7789ffb..21d25ecdab2c62 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
@@ -6,7 +6,7 @@
*/
import React, { useState, useEffect, useCallback, useMemo, memo } from 'react';
-import { omit } from 'lodash';
+import { isEmpty, omit } from 'lodash';
import { useRouteMatch } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
@@ -103,7 +103,7 @@ export const EditPackagePolicyForm = memo<{
const {
// data
- agentPolicy,
+ agentPolicies,
isLoadingData,
loadingError,
packagePolicy,
@@ -134,22 +134,26 @@ export const EditPackagePolicyForm = memo<{
return getNewSecrets({ packageInfo, packagePolicy });
}, [packageInfo, packagePolicy]);
- const policyId = agentPolicy?.id ?? '';
+ const policyIds = agentPolicies.map((policy) => policy.id);
// Retrieve agent count
const [agentCount, setAgentCount] = useState(0);
useEffect(() => {
const getAgentCount = async () => {
- const { data } = await sendGetAgentStatus({ policyId });
- if (data?.results.total) {
- setAgentCount(data.results.total);
+ let count = 0;
+ for (const policyId of policyIds) {
+ const { data } = await sendGetAgentStatus({ policyId });
+ if (data?.results.total) {
+ count += data.results.total;
+ }
}
+ setAgentCount(count);
};
- if (isFleetEnabled && policyId) {
+ if (isFleetEnabled && policyIds.length > 0) {
getAgentCount();
}
- }, [policyId, isFleetEnabled]);
+ }, [policyIds, isFleetEnabled]);
const handleExtensionViewOnChange = useCallback<
PackagePolicyEditExtensionComponentProps['onChange']
@@ -170,25 +174,25 @@ export const EditPackagePolicyForm = memo<{
// if `from === 'edit'` then it links back to Policy Details
// if `from === 'package-edit'`, or `upgrade-from-integrations-policy-list` then it links back to the Integration Policy List
const cancelUrl = useMemo((): string => {
- if (packageInfo && policyId) {
+ if (packageInfo && policyIds.length > 0) {
return from === 'package-edit'
? getHref('integration_details_policies', {
pkgkey: pkgKeyFromPackageInfo(packageInfo!),
})
- : getHref('policy_details', { policyId });
+ : getHref('policy_details', { policyId: policyIds[0] });
}
return '/';
- }, [from, getHref, packageInfo, policyId]);
+ }, [from, getHref, packageInfo, policyIds]);
const successRedirectPath = useMemo(() => {
- if (packageInfo && policyId) {
+ if (packageInfo && policyIds.length > 0) {
return from === 'package-edit' || from === 'upgrade-from-integrations-policy-list'
? getHref('integration_details_policies', {
pkgkey: pkgKeyFromPackageInfo(packageInfo!),
})
- : getHref('policy_details', { policyId });
+ : getHref('policy_details', { policyId: policyIds[0] });
}
return '/';
- }, [from, getHref, packageInfo, policyId]);
+ }, [from, getHref, packageInfo, policyIds]);
useHistoryBlock(isEdited);
@@ -197,7 +201,7 @@ export const EditPackagePolicyForm = memo<{
setFormState('INVALID');
return;
}
- if (agentCount !== 0 && policyId !== AGENTLESS_POLICY_ID && formState !== 'CONFIRM') {
+ if (agentCount !== 0 && !policyIds.includes(AGENTLESS_POLICY_ID) && formState !== 'CONFIRM') {
setFormState('CONFIRM');
return;
}
@@ -215,11 +219,11 @@ export const EditPackagePolicyForm = memo<{
}),
'data-test-subj': 'policyUpdateSuccessToast',
text:
- agentCount && agentPolicy
+ agentCount && agentPolicies.length > 0
? i18n.translate('xpack.fleet.editPackagePolicy.updatedNotificationMessage', {
- defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyName}'' policy`,
+ defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyNames}'' policy`,
values: {
- agentPolicyName: agentPolicy.name,
+ agentPolicyNames: agentPolicies.map((policy) => policy.name).join(', '),
},
})
: undefined,
@@ -276,7 +280,7 @@ export const EditPackagePolicyForm = memo<{
const layoutProps = {
from: extensionView?.useLatestPackageVersion && isUpgrade ? 'upgrade-from-extension' : from,
cancelUrl,
- agentPolicy,
+ agentPolicies,
packageInfo,
tabs: tabsViews?.length
? [
@@ -302,11 +306,11 @@ export const EditPackagePolicyForm = memo<{
const configurePackage = useMemo(
() =>
- agentPolicy && packageInfo ? (
+ agentPolicies && packageInfo ? (
<>
{selectedTab === 0 && (
) : null,
[
- agentPolicy,
+ agentPolicies,
packageInfo,
packagePolicy,
updatePackagePolicy,
@@ -368,7 +372,7 @@ export const EditPackagePolicyForm = memo<{
const replaceConfigurePackage = replaceDefineStepView && originalPackagePolicy && packageInfo && (
{isLoadingData ? (
- ) : loadingError || !agentPolicy || !packageInfo ? (
+ ) : loadingError || isEmpty(agentPolicies) || !packageInfo ? (
{formState === 'CONFIRM' && (
setFormState('VALID')}
/>
@@ -453,7 +457,7 @@ export const EditPackagePolicyForm = memo<{
- {agentPolicy && packageInfo && formState === 'INVALID' ? (
+ {agentPolicies && packageInfo && formState === 'INVALID' ? (
=> {
if (!data?.items) {
return [];
}
- const newPolicies = data.items.map(({ agentPolicy, packagePolicy }) => {
+ const newPolicies = data.items.map(({ agentPolicies, packagePolicy }) => {
const hasUpgrade = isPackagePolicyUpgradable(packagePolicy);
return {
- agentPolicy,
+ agentPolicies,
packagePolicy: {
...packagePolicy,
hasUpgrade,
@@ -140,8 +140,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
return newPolicies;
}, [data?.items, isPackagePolicyUpgradable]);
- const showAddAgentHelpForPackagePolicyId = packageAndAgentPolicies.find(
- ({ agentPolicy }) => agentPolicy?.id === showAddAgentHelpForPolicyId
+ const showAddAgentHelpForPackagePolicyId = packageAndAgentPolicies.find(({ agentPolicies }) =>
+ agentPolicies.find((agentPolicy) => agentPolicy.id === showAddAgentHelpForPolicyId)
)?.packagePolicy?.id;
// Handle the "add agent" link displayed in post-installation toast notifications in the case
// where a user is clicking the link while on the package policies listing page
@@ -184,7 +184,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.version', {
defaultMessage: 'Version',
}),
- render(_version, { agentPolicy, packagePolicy }) {
+ render(_version, { agentPolicies, packagePolicy }) {
return (
@@ -197,13 +197,13 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
- {agentPolicy && packagePolicy.hasUpgrade && (
+ {agentPolicies.length > 0 && packagePolicy.hasUpgrade && (
+ render(id, { agentPolicies }) {
+ return agentPolicies.length > 0 ? (
+ // TODO: handle multiple agent policies
+
) : (
);
@@ -263,10 +264,11 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentCount', {
defaultMessage: 'Agents',
}),
- render({ agentPolicy, packagePolicy }: InMemoryPackagePolicyAndAgentPolicy) {
- if (!agentPolicy) {
+ render({ agentPolicies, packagePolicy }: InMemoryPackagePolicyAndAgentPolicy) {
+ if (agentPolicies.length === 0) {
return null;
}
+ const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies
const canAddAgentsForPolicy = policyHasFleetServer(agentPolicy)
? canAddFleetServers
: canAddAgents;
@@ -288,7 +290,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
}),
width: '8ch',
align: 'right',
- render({ agentPolicy, packagePolicy }) {
+ render({ agentPolicies, packagePolicy }) {
+ const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies
return (
);
}
- const selectedPolicies = packageAndAgentPolicies.find(
- ({ agentPolicy: policy }) => policy?.id === flyoutOpenForPolicyId
+ const selectedPolicies = packageAndAgentPolicies.find(({ agentPolicies: policies }) =>
+ policies.find((policy) => policy.id === flyoutOpenForPolicyId)
);
- const agentPolicy = selectedPolicies?.agentPolicy;
+ const agentPolicies = selectedPolicies?.agentPolicies;
const packagePolicy = selectedPolicies?.packagePolicy;
return (
@@ -364,14 +367,14 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
/>
- {flyoutOpenForPolicyId && agentPolicy && !isLoading && (
+ {flyoutOpenForPolicyId && agentPolicies && !isLoading && (
{
setFlyoutOpenForPolicyId(null);
const { addAgentToPolicyId, ...rest } = parse(search);
history.replace({ search: stringify(rest) });
}}
- agentPolicy={agentPolicy}
+ agentPolicy={agentPolicies[0]}
isIntegrationFlow={true}
installedPackagePolicy={{
name: packagePolicy?.package?.name || '',
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts
index 560a2900ae406f..f975933d53d99b 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts
@@ -19,13 +19,9 @@ import { agentPolicyRouteService } from '../../../../../services';
import { useGetPackagePolicies, useConditionalRequest } from '../../../../../hooks';
import type { SendConditionalRequestConfig } from '../../../../../hooks';
-export interface PackagePolicyEnriched extends PackagePolicy {
- _agentPolicy: GetAgentPoliciesResponseItem | undefined;
-}
-
export interface PackagePolicyAndAgentPolicy {
packagePolicy: PackagePolicy;
- agentPolicy: GetAgentPoliciesResponseItem;
+ agentPolicies: GetAgentPoliciesResponseItem[];
}
type GetPackagePoliciesWithAgentPolicy = Omit & {
@@ -34,7 +30,7 @@ type GetPackagePoliciesWithAgentPolicy = Omit {
return {
packagePolicy,
- agentPolicy: agentPoliciesById[packagePolicy.policy_ids[0]], // TODO multiple agent policies
+ agentPolicies: packagePolicy.policy_ids
+ .map((policyId: string) => agentPoliciesById[policyId])
+ .filter((policy) => !!policy),
};
}
);
diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts b/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts
index cbccc54273e298..837222dae103fa 100644
--- a/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts
+++ b/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts
@@ -65,6 +65,15 @@ export const useBulkGetAgentPoliciesQuery = (ids: string[], options?: { full?: b
);
};
+export const sendBulkGetAgentPolicies = (ids: string[], options?: { full?: boolean }) => {
+ return sendRequest({
+ path: agentPolicyRouteService.getBulkGetPath(),
+ method: 'post',
+ body: JSON.stringify({ ids, full: options?.full }),
+ version: API_VERSIONS.public.v1,
+ });
+};
+
export const sendGetAgentPolicies = (query?: GetAgentPoliciesRequest['query']) => {
return sendRequest({
path: agentPolicyRouteService.getListPath(),