Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Endpoint] Assign and remove Event Filters by policy based on user license #122434

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
* 2.0.
*/

import React, { memo } from 'react';
import { EuiEmptyPrompt, EuiPageTemplate, EuiLink } from '@elastic/eui';
import React, { memo, useCallback } from 'react';
import { EuiButton, EuiEmptyPrompt, EuiPageTemplate, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
// TODO: Uncomment this when assign functionality through the flyout is done
// import { usePolicyDetailsNavigateCallback } from '../../policy_hooks';
import { usePolicyDetailsNavigateCallback } from '../../policy_hooks';
import { useGetLinkTo } from './use_policy_event_filters_empty_hooks';
import { useUserPrivileges } from '../../../../../../common/components/user_privileges';

Expand All @@ -22,15 +21,14 @@ export const PolicyEventFiltersEmptyUnassigned = memo<CommonProps>(({ policyId,
const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName);

// TODO: Uncomment this when assign functionality through the flyout is done
// const navigateCallback = usePolicyDetailsNavigateCallback();
// const onClickPrimaryButtonHandler = useCallback(
// () =>
// navigateCallback({
// show: 'list',
// }),
// [navigateCallback]
// );
const navigateCallback = usePolicyDetailsNavigateCallback();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for uncomment this! I totally forgot it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks back to you. 👏 good thing you'd placed these lines already. I'd to uncomment it and it worked out of the box. 😬

const onClickPrimaryButtonHandler = useCallback(
() =>
navigateCallback({
show: 'list',
}),
[navigateCallback]
);
return (
<EuiPageTemplate template="centeredContent">
<EuiEmptyPrompt
Expand All @@ -54,18 +52,17 @@ export const PolicyEventFiltersEmptyUnassigned = memo<CommonProps>(({ policyId,
actions={[
...(canCreateArtifactsByPolicy
? [
// TODO: Uncomment this when assign functionality through the flyout is done
// <EuiButton
// color="primary"
// fill
// onClick={onClickPrimaryButtonHandler}
// data-test-subj="assign-ta-button"
// >
// <FormattedMessage
// id="xpack.securitySolution.endpoint.policy.eventFilters.empty.unassigned.primaryAction"
// defaultMessage="Assign event filters"
// />
// </EuiButton>,
<EuiButton
color="primary"
fill
onClick={onClickPrimaryButtonHandler}
data-test-subj="assign-event-filter-button"
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.eventFilters.empty.unassigned.primaryAction"
defaultMessage="Assign event filters"
/>
</EuiButton>,
]
: []),
// eslint-disable-next-line @elastic/eui/href-or-on-click
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
AppContextTestRender,
createAppRootMockRenderer,
} from '../../../../../../common/mock/endpoint';
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';

import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types';
import { parsePoliciesAndFilterToKql } from '../../../../../common/utils';
import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils';
import { getFoundExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';

let render: () => ReturnType<AppContextTestRender['render']>;
let mockedContext: AppContextTestRender;
Expand All @@ -34,12 +36,12 @@ describe('Policy event filters layout', () => {

afterEach(() => reactTestingLibrary.cleanup());

it('should renders layout with a loader', async () => {
it('should render layout with a loader', async () => {
const component = render();
expect(component.getByTestId('policy-event-filters-loading-spinner')).toBeTruthy();
});

it('should renders layout with no assigned event filters data when there are not event filters', async () => {
it('should render layout with no assigned event filters data when there are not event filters', async () => {
mockedApi.responseProvider.eventFiltersList.mockReturnValue(
getFoundExceptionListItemSchemaMock(0)
);
Expand All @@ -48,7 +50,7 @@ describe('Policy event filters layout', () => {
expect(await component.findByTestId('policy-event-filters-empty-unexisting')).not.toBeNull();
});

it('should renders layout with no assigned event filters data when there are event filters', async () => {
it('should render layout with no assigned event filters data when there are event filters', async () => {
mockedApi.responseProvider.eventFiltersList.mockImplementation(
// @ts-expect-error
(args) => {
Expand All @@ -69,7 +71,7 @@ describe('Policy event filters layout', () => {
expect(await component.findByTestId('policy-event-filters-empty-unassigned')).not.toBeNull();
});

it('should renders layout with data', async () => {
it('should render layout with data', async () => {
mockedApi.responseProvider.eventFiltersList.mockReturnValue(
getFoundExceptionListItemSchemaMock(3)
);
Expand All @@ -80,4 +82,47 @@ describe('Policy event filters layout', () => {
'3 event filters'
);
});

it('should hide `Assign event filters to policy` on empty state with unassigned policies when downgraded to a gold or below license', () => {
getEndpointPrivilegesInitialStateMock({
canCreateArtifactsByPolicy: false,
});
mockedApi.responseProvider.eventFiltersList.mockReturnValue(
getFoundExceptionListItemSchemaMock(0)
);

const component = render();
mockedContext.history.push(getPolicyDetailsArtifactsListPath(policyItem.id));
expect(component.queryByTestId('assign-event-filter-button')).toBeNull();
});

it('should hide the `Assign event filters to policy` button license is downgraded to gold or below', () => {
getEndpointPrivilegesInitialStateMock({
canCreateArtifactsByPolicy: false,
});
mockedApi.responseProvider.eventFiltersList.mockReturnValue(
getFoundExceptionListItemSchemaMock(5)
);

const component = render();
mockedContext.history.push(getPolicyDetailsArtifactsListPath(policyItem.id));

expect(component.queryByTestId('eventFilters-assign-button')).toBeNull();
});

it('should hide the `Assign event filters` flyout when license is downgraded to gold or below', () => {
getEndpointPrivilegesInitialStateMock({
canCreateArtifactsByPolicy: false,
});
mockedApi.responseProvider.eventFiltersList.mockReturnValue(
getFoundExceptionListItemSchemaMock(2)
);

const component = render();
mockedContext.history.push(
`${getPolicyDetailsArtifactsListPath(policyItem.id)}/eventFilters?show=list`
);

expect(component.queryByTestId('eventFilters-assign-flyout')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoin
import { getEventFiltersListPath } from '../../../../../common/routing';
import { useGetAllAssignedEventFilters, useGetAllEventFilters } from '../hooks';
import { ManagementPageLoader } from '../../../../../components/management_page_loader';
import { useUserPrivileges } from '../../../../../../common/components/user_privileges';
import { PolicyEventFiltersEmptyUnassigned, PolicyEventFiltersEmptyUnexisting } from '../empty';
import {
usePolicyDetailsSelector,
Expand All @@ -40,6 +41,7 @@ export const PolicyEventFiltersLayout = React.memo<PolicyEventFiltersLayoutProps
({ policyItem }) => {
const { getAppUrl } = useAppUrl();
const navigateCallback = usePolicyDetailsEventFiltersNavigateCallback();
const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);

const { data: allAssigned, isLoading: isLoadingAllAssigned } = useGetAllAssignedEventFilters(
Expand All @@ -56,6 +58,25 @@ export const PolicyEventFiltersLayout = React.memo<PolicyEventFiltersLayoutProps
navigateCallback({ show: undefined });
}, [navigateCallback]);

const assignToPolicyButton = useMemo(
ashokaditya marked this conversation as resolved.
Show resolved Hide resolved
() => (
<EuiButton
fill
iconType="plusInCircle"
data-test-subj="eventFilters-assign-button"
onClick={handleOnClickAssignButton}
>
{i18n.translate(
'xpack.securitySolution.endpoint.policy.eventFilters.layout.assignToPolicy',
{
defaultMessage: 'Assign event filters to policy',
}
)}
</EuiButton>
),
[handleOnClickAssignButton]
);

const aboutInfo = useMemo(() => {
const link = (
<EuiLink
Expand Down Expand Up @@ -122,22 +143,10 @@ export const PolicyEventFiltersLayout = React.memo<PolicyEventFiltersLayoutProps
</EuiText>
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<EuiButton
fill
iconType="plusInCircle"
data-test-subj="eventFilters-assign-button"
onClick={handleOnClickAssignButton}
>
{i18n.translate(
'xpack.securitySolution.endpoint.policy.eventFilters.layout.assignToPolicy',
{
defaultMessage: 'Assign event filters to policy',
}
)}
</EuiButton>
{canCreateArtifactsByPolicy && assignToPolicyButton}
</EuiPageHeaderSection>
</EuiPageHeader>
{urlParams.show === 'list' && (
{canCreateArtifactsByPolicy && urlParams.show === 'list' && (
<PolicyEventFiltersFlyout policyItem={policyItem} onClose={handleOnCloseFlyout} />
)}
<EuiSpacer size="l" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_ut
import { PolicyEventFiltersList } from './policy_event_filters_list';
import { parseQueryFilterToKQL, parsePoliciesAndFilterToKql } from '../../../../../common/utils';
import { SEARCHABLE_FIELDS } from '../../../../event_filters/constants';
import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';

const endpointGenerator = new EndpointDocGenerator('seed');
const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({
Expand Down Expand Up @@ -133,4 +134,19 @@ describe('Policy details event filters list', () => {
);
expect(renderResult.queryByTestId('view-full-details-action')).toBeTruthy();
});

it('does not show remove option in actions menu if license is downgraded to gold or below', async () => {
getEndpointPrivilegesInitialStateMock({
canCreateArtifactsByPolicy: false,
});
mockedApi.responseProvider.eventFiltersList.mockReturnValue(
getFoundExceptionListItemSchemaMock()
);
await render();
userEvent.click(
renderResult.getByTestId('eventFilters-collapsed-list-card-header-actions-button')
);

expect(renderResult.queryByTestId('remove-from-policy-action')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoin
import { PolicyEventFiltersDeleteModal } from '../delete_modal';
import { isGlobalPolicyEffected } from '../../../../../components/effected_policy_select/utils';
import { getEventFiltersListPath } from '../../../../../common/routing';
import { useUserPrivileges } from '../../../../../../common/components/user_privileges';

interface PolicyEventFiltersListProps {
policy: ImmutableObject<PolicyData>;
}
export const PolicyEventFiltersList = React.memo<PolicyEventFiltersListProps>(({ policy }) => {
const { getAppUrl } = useAppUrl();
const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
const policiesRequest = useGetEndpointSpecificPolicies();
const navigateCallback = usePolicyDetailsEventFiltersNavigateCallback();
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);
Expand Down Expand Up @@ -142,7 +144,7 @@ export const PolicyEventFiltersList = React.memo<PolicyEventFiltersListProps>(({
};
return {
expanded: expandedItemsMap.get(item.id) || false,
actions: [fullDetailsAction, deleteAction],
actions: canCreateArtifactsByPolicy ? [fullDetailsAction, deleteAction] : [fullDetailsAction],
policies: artifactCardPolicies,
};
};
Expand Down