diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index 1497b1bb0589e..6b0a7c512d197 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -53,7 +53,7 @@ import { UninstallCommandFlyout } from '../../../../../../components'; import type { ValidationResults } from '../agent_policy_validation'; import { ExperimentalFeaturesService } from '../../../../services'; - +import { useAgentPolicyFormContext } from '../agent_policy_form'; import { policyHasEndpointSecurity as hasElasticDefend } from '../../../../../../../common/services'; import { @@ -127,6 +127,8 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = const isManagedorAgentlessPolicy = agentPolicy.is_managed === true || agentPolicy?.supports_agentless === true; + const agentPolicyFormContect = useAgentPolicyFormContext(); + const AgentTamperProtectionSectionContent = useMemo( () => ( = /> } > - - { + if (newValue.length === 0) { + return; } - onChange={(newValue) => { - if (newValue.length === 0) { - return; - } - updateAgentPolicy({ - space_ids: newValue, - }); - }} - /> - + updateAgentPolicy({ + space_ids: newValue, + }); + }} + /> ) : null} { + beforeEach(() => { + jest.mocked(useAgentPoliciesSpaces).mockReturnValue({ + data: { + items: [ + { + name: 'Default', + id: 'default', + }, + { + name: 'Test', + id: 'test', + }, + ], + }, + } as any); + }); + function render() { + const renderer = createFleetTestRendererMock(); + const onChange = jest.fn(); + const setInvalidSpaceError = jest.fn(); + const result = renderer.render( + + ); + + return { + result, + onChange, + setInvalidSpaceError, + }; + } + + it('should render invalid space errors', () => { + const { result, onChange, setInvalidSpaceError } = render(); + const inputEl = result.getByTestId('comboBoxSearchInput'); + fireEvent.change(inputEl, { + target: { value: 'invalidSpace' }, + }); + fireEvent.keyDown(inputEl, { key: 'Enter', code: 'Enter' }); + expect(result.container).toHaveTextContent('invalidSpace is not a valid space.'); + expect(onChange).not.toBeCalled(); + expect(setInvalidSpaceError).toBeCalledWith(true); + }); + + it('should clear invalid space errors', () => { + const { result, setInvalidSpaceError } = render(); + const inputEl = result.getByTestId('comboBoxSearchInput'); + fireEvent.change(inputEl, { + target: { value: 'invalidSpace' }, + }); + fireEvent.keyDown(inputEl, { key: 'Enter', code: 'Enter' }); + expect(result.container).toHaveTextContent('invalidSpace is not a valid space.'); + fireEvent.change(inputEl, { + target: { value: '' }, + }); + fireEvent.keyDown(inputEl, { key: 'Enter', code: 'Enter' }); + expect(result.container).not.toHaveTextContent('invalidSpace is not a valid space.'); + expect(setInvalidSpaceError).toBeCalledWith(false); + }); + + it('should accept valid space', () => { + const { result, onChange, setInvalidSpaceError } = render(); + const inputEl = result.getByTestId('comboBoxSearchInput'); + fireEvent.change(inputEl, { + target: { value: 'test' }, + }); + fireEvent.keyDown(inputEl, { key: 'Enter', code: 'Enter' }); + expect(result.container).not.toHaveTextContent('test is not a valid space.'); + expect(onChange).toBeCalledWith(['test']); + expect(setInvalidSpaceError).not.toBeCalledWith(true); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/space_selector.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/space_selector.tsx index 0532c5306d50f..53c7ed1d8226d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/space_selector.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/space_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { type EuiComboBoxOptionOption, EuiHealth } from '@elastic/eui'; +import { type EuiComboBoxOptionOption, EuiHealth, EuiFormRow } from '@elastic/eui'; import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; @@ -16,11 +16,19 @@ export interface SpaceSelectorProps { value: string[]; onChange: (newVal: string[]) => void; isDisabled?: boolean; + setInvalidSpaceError?: (hasError: boolean) => void; } -export const SpaceSelector: React.FC = ({ value, onChange, isDisabled }) => { +export const SpaceSelector: React.FC = ({ + setInvalidSpaceError, + value, + onChange, + isDisabled, +}) => { const res = useAgentPoliciesSpaces(); + const [error, setError] = React.useState(); + const renderOption = React.useCallback( (option: any, searchValue: string, contentClassName: string) => ( @@ -57,20 +65,41 @@ export const SpaceSelector: React.FC = ({ value, onChange, i }, [options, value, res.isInitialLoading]); return ( - { - onChange(newOptions.map(({ key }) => key as string)); - }} - /> + key="space" + error={error} + isDisabled={isDisabled} + isInvalid={Boolean(error)} + > + { + const newError = + searchValue.length === 0 || hasMatchingOptions + ? undefined + : i18n.translate('xpack.fleet.agentPolicies.spaceSelectorInvalid', { + defaultMessage: '{space} is not a valid space.', + values: { space: searchValue }, + }); + setError(newError); + if (setInvalidSpaceError) { + setInvalidSpaceError(!!newError); + } + }} + onChange={(newOptions) => { + onChange(newOptions.map(({ key }) => key as string)); + }} + /> + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index b437d61f64c58..8e97afcaa4d66 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -45,12 +45,14 @@ interface Props { isEditing?: boolean; // form error state is passed up to the form updateAdvancedSettingsHasErrors: (hasErrors: boolean) => void; + setInvalidSpaceError: (hasErrors: boolean) => void; } const AgentPolicyFormContext = React.createContext< | { agentPolicy: Partial & { [key: string]: any }; updateAgentPolicy: (u: Partial) => void; updateAdvancedSettingsHasErrors: (hasErrors: boolean) => void; + setInvalidSpaceError: (hasErrors: boolean) => void; } | undefined >(undefined); @@ -67,6 +69,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({ validation, isEditing = false, updateAdvancedSettingsHasErrors, + setInvalidSpaceError, }) => { const authz = useAuthz(); const isDisabled = !authz.fleet.allAgentPolicies; @@ -97,7 +100,12 @@ export const AgentPolicyForm: React.FunctionComponent = ({ return ( {!isEditing ? ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx index 6e4f1e06b45a0..91cd710db4343 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx @@ -90,6 +90,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( allowedNamespacePrefixes: spaceSettings?.allowedNamespacePrefixes, }); const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState(false); + const [hasInvalidSpaceError, setInvalidSpaceError] = useState(false); const updateAgentPolicy = (updatedFields: Partial) => { setAgentPolicy({ @@ -183,6 +184,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( validation={validation} isEditing={true} updateAdvancedSettingsHasErrors={setHasAdvancedSettingsErrors} + setInvalidSpaceError={setInvalidSpaceError} /> {hasChanges ? ( @@ -219,7 +221,8 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( isDisabled={ isLoading || Object.keys(validation).length > 0 || - hasAdvancedSettingsErrors + hasAdvancedSettingsErrors || + hasInvalidSpaceError } btnProps={{ color: 'text', @@ -242,7 +245,8 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( !hasAllAgentPoliciesPrivileges || isLoading || Object.keys(validation).length > 0 || - hasAdvancedSettingsErrors + hasAdvancedSettingsErrors || + hasInvalidSpaceError } data-test-subj="agentPolicyDetailsSaveButton" iconType="save" diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx index f147f7e112ea1..a5538e7e0fa30 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx @@ -61,6 +61,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ allowedNamespacePrefixes: spaceSettings?.allowedNamespacePrefixes, }); const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState(false); + const [hasInvalidSpaceError, setInvalidSpaceError] = useState(false); const updateAgentPolicy = (updatedFields: Partial) => { setAgentPolicy({ @@ -104,6 +105,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ updateSysMonitoring={(newValue) => setWithSysMonitoring(newValue)} validation={validation} updateAdvancedSettingsHasErrors={setHasAdvancedSettingsErrors} + setInvalidSpaceError={setInvalidSpaceError} /> ); @@ -130,7 +132,10 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ 0 || hasAdvancedSettingsErrors + isLoading || + Object.keys(validation).length > 0 || + hasAdvancedSettingsErrors || + hasInvalidSpaceError } description={i18n.translate( 'xpack.fleet.createAgentPolicy.devtoolsRequestDescription', @@ -150,7 +155,8 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ !hasFleetAllAgentPoliciesPrivileges || isLoading || Object.keys(validation).length > 0 || - hasAdvancedSettingsErrors + hasAdvancedSettingsErrors || + hasInvalidSpaceError } onClick={async () => { setIsLoading(true);