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] Refactor host isolation exceptions list page to now use <ArtifactListPage/> component #129099

Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e7cc3b3
initial commit wiht some cleanup
paul-tavares Mar 31, 2022
937877c
Adjust labels to match current implementation
paul-tavares Mar 31, 2022
7ee5470
add array of searchable fields
paul-tavares Mar 31, 2022
956b5f0
Generic FormatError component for formatting error Objects
paul-tavares Mar 31, 2022
48398ba
Adjust Host Isolation Exceptions Form to the ArtifactListPage component
paul-tavares Mar 31, 2022
0ed0abb
clean up un-used components ++ hooks
paul-tavares Mar 31, 2022
12ac093
Additional clean up of file content
paul-tavares Mar 31, 2022
24c1f00
EffectedPolicySelect support for `disabled` prop
paul-tavares Mar 31, 2022
cb56284
Ensure Form fields are disabled when API is in flight
paul-tavares Mar 31, 2022
b696e92
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 1, 2022
36bcdff
new ExceptionsList HTTP mocks
paul-tavares Apr 1, 2022
ef65a42
Tests for the List to validate search/filter
paul-tavares Apr 1, 2022
84e0aff
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 1, 2022
7840222
additional test utilities for EffectedPolicySelect component
paul-tavares Apr 1, 2022
5db499a
Form tests
paul-tavares Apr 1, 2022
71854b0
Tests for FormattedError
paul-tavares Apr 1, 2022
7453c4a
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 4, 2022
c924f27
Fix i18n entries
paul-tavares Apr 4, 2022
5812b67
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 4, 2022
53a325d
Delete host isolation exceptions store and adjust global Management s…
paul-tavares Apr 4, 2022
722b8d7
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 5, 2022
b688d50
New url routing utilities for Artifact List Page
paul-tavares Apr 5, 2022
b4bf73b
Refactor Host Isolation Exceptions routing function to use new Artifa…
paul-tavares Apr 5, 2022
3ac4b84
Remove custom host isolation exceptions hook and use generic one
paul-tavares Apr 5, 2022
bb375e1
Remove unnecessary service functions and adjust tests in policy details
paul-tavares Apr 5, 2022
2ae32df
Add http mock for exceptions summary api
paul-tavares Apr 5, 2022
e7518f5
Refactor `useCanSeeHostIsolationExceptionsMenu()` hook to use generic…
paul-tavares Apr 5, 2022
ec07ea2
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 6, 2022
acc0983
add props to ArtifactListPage to allow for hiding actions like create…
paul-tavares Apr 6, 2022
3ca47ed
hide create/edit actions on host isolation list page if user can't is…
paul-tavares Apr 6, 2022
7529671
Additional tests for ArtifactLIstPage
paul-tavares Apr 6, 2022
344b2d0
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 6, 2022
6ce6b00
Split out useUserPrivileges() mock implementation function for reuse …
paul-tavares Apr 7, 2022
f3d33f3
add generic `getFirstCard()` test utility to ArtifactListPage mocks
paul-tavares Apr 7, 2022
3ff6457
Fix form tests and add additional tests to list page
paul-tavares Apr 7, 2022
1163df0
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 7, 2022
739f5ba
some adjustments to exports and jsdocs
paul-tavares Apr 7, 2022
176c48d
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 11, 2022
1f6ca0a
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 18, 2022
53ae8de
fix stuff after merge from main
paul-tavares Apr 18, 2022
a2d1999
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 18, 2022
5dba32d
Fix TS errors
paul-tavares Apr 18, 2022
4d853ea
Fix issue with test render keeping state from prior API calls (react-…
paul-tavares Apr 18, 2022
eb307c2
Fix tests for EndpointPackageCustomExtension
paul-tavares Apr 18, 2022
2d5864c
Merge remote-tracking branch 'upstream/main' into task/olm-3094-move-…
paul-tavares Apr 18, 2022
b16c443
Merge branch 'main' into task/olm-3094-move-host-isolation-exceptions…
kibanamachine Apr 18, 2022
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 @@ -6,6 +6,7 @@
*/

import { isEmpty } from 'lodash/fp';
// FIXME: Remove references to `querystring`
// eslint-disable-next-line import/no-nodejs-modules
import querystring from 'querystring';
import { generatePath } from 'react-router-dom';
Expand All @@ -14,26 +15,26 @@ import { ArtifactListPageUrlParams } from '../components/artifact_list_page';
import { paginationFromUrlParams } from '../components/hooks/use_url_pagination';
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
import { EventFiltersPageLocation } from '../pages/event_filters/types';
import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types';
import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types';
import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
import { AdministrationSubTab } from '../types';
import {
MANAGEMENT_DEFAULT_PAGE,
MANAGEMENT_DEFAULT_PAGE_SIZE,
MANAGEMENT_PAGE_SIZE_OPTIONS,
MANAGEMENT_ROUTING_BLOCKLIST_PATH,
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
MANAGEMENT_ROUTING_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_BLOCKLIST_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH,
} from './constants';
import { isDefaultOrMissing, getArtifactListPageUrlPath } from './url_routing';

// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
Expand Down Expand Up @@ -149,10 +150,6 @@ export const getPolicyEventFiltersPath = (
)}`;
};

const isDefaultOrMissing = <T>(value: T | undefined, defaultValue: T) => {
return value === undefined || value === defaultValue;
};

const normalizeTrustedAppsPageLocation = (
location?: Partial<TrustedAppsListPageLocation>
): Partial<TrustedAppsListPageLocation> => {
Expand Down Expand Up @@ -219,52 +216,6 @@ const normalizeEventFiltersPageLocation = (
}
};

const normalizBlocklistsPageLocation = (
location?: Partial<ArtifactListPageUrlParams>
): Partial<ArtifactListPageUrlParams> => {
if (location) {
return {
...(!isDefaultOrMissing(location.page, MANAGEMENT_DEFAULT_PAGE)
? { page: location.page }
: {}),
...(!isDefaultOrMissing(location.pageSize, MANAGEMENT_DEFAULT_PAGE_SIZE)
? { pageSize: location.pageSize }
: {}),
...(!isDefaultOrMissing(location.show, undefined) ? { show: location.show } : {}),
...(!isDefaultOrMissing(location.itemId, undefined) ? { id: location.itemId } : {}),
...(!isDefaultOrMissing(location.filter, '') ? { filter: location.filter } : ''),
...(!isDefaultOrMissing(location.includedPolicies, '')
? { includedPolicies: location.includedPolicies }
: ''),
};
} else {
return {};
}
};

const normalizeHostIsolationExceptionsPageLocation = (
location?: Partial<HostIsolationExceptionsPageLocation>
): Partial<EventFiltersPageLocation> => {
if (location) {
return {
...(!isDefaultOrMissing(location.page_index, MANAGEMENT_DEFAULT_PAGE)
? { page_index: location.page_index }
: {}),
...(!isDefaultOrMissing(location.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE)
? { page_size: location.page_size }
: {}),
...(!isDefaultOrMissing(location.show, undefined) ? { show: location.show } : {}),
...(!isDefaultOrMissing(location.id, undefined) ? { id: location.id } : {}),
...(!isDefaultOrMissing(location.filter, '') ? { filter: location.filter } : ''),
...(!isDefaultOrMissing(location.included_policies, '')
? { included_policies: location.included_policies }
: ''),
};
} else {
return {};
}
};

/**
* Given an object with url params, and a given key, return back only the first param value (case multiples were defined)
* @param query
Expand Down Expand Up @@ -395,33 +346,14 @@ export const getEventFiltersListPath = (location?: Partial<EventFiltersPageLocat
)}`;
};

export const extractHostIsolationExceptionsPageLocation = (
query: querystring.ParsedUrlQuery
): HostIsolationExceptionsPageLocation => {
const showParamValue = extractFirstParamValue(
query,
'show'
) as HostIsolationExceptionsPageLocation['show'];

return {
...extractListPaginationParams(query),
included_policies: extractIncludedPolicies(query),
show:
showParamValue && ['edit', 'create'].includes(showParamValue) ? showParamValue : undefined,
id: extractFirstParamValue(query, 'id'),
};
};

export const getHostIsolationExceptionsListPath = (
location?: Partial<HostIsolationExceptionsPageLocation>
location?: Partial<ArtifactListPageUrlParams>
): string => {
const path = generatePath(MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH, {
tabName: AdministrationSubTab.hostIsolationExceptions,
});

return `${path}${appendSearch(
querystring.stringify(normalizeHostIsolationExceptionsPageLocation(location))
)}`;
return getArtifactListPageUrlPath(path, location);
};

export const getPolicyHostIsolationExceptionsPath = (
Expand All @@ -442,7 +374,7 @@ export const getBlocklistsListPath = (location?: Partial<ArtifactListPageUrlPara
tabName: AdministrationSubTab.blocklist,
});

return `${path}${appendSearch(querystring.stringify(normalizBlocklistsPageLocation(location)))}`;
return getArtifactListPageUrlPath(path, location);
};

export const getPolicyBlocklistsPath = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.
*/

// FIXME: Remove references to `querystring`
// eslint-disable-next-line import/no-nodejs-modules
import querystring from 'querystring';
import { ArtifactListPageUrlParams } from '../../components/artifact_list_page';
import {
isDefaultOrMissing,
extractFirstParamValue,
extractPageSizeNumber,
extractPageNumber,
} from './utils';
import { MANAGEMENT_DEFAULT_PAGE_SIZE } from '../constants';
import { appendSearch } from '../../../common/components/link_to/helpers';

const SHOW_PARAM_ALLOWED_VALUES: ReadonlyArray<Required<ArtifactListPageUrlParams>['show']> = [
'edit',
'create',
];

/**
* Normalizes the URL search params by dropping any that are either undefined or whose value is
* equal to the default value.
* @param urlSearchParams
*/
const normalizeArtifactListPageUrlSearchParams = (
urlSearchParams: Partial<ArtifactListPageUrlParams> = {}
): Partial<ArtifactListPageUrlParams> => {
return {
...(!isDefaultOrMissing(urlSearchParams.page, 1) ? { page: urlSearchParams.page } : {}),
...(!isDefaultOrMissing(urlSearchParams.pageSize, MANAGEMENT_DEFAULT_PAGE_SIZE)
? { pageSize: urlSearchParams.pageSize }
: {}),
...(!isDefaultOrMissing(urlSearchParams.show, undefined) ? { show: urlSearchParams.show } : {}),
...(!isDefaultOrMissing(urlSearchParams.itemId, undefined)
? { itemId: urlSearchParams.itemId }
: {}),
...(!isDefaultOrMissing(urlSearchParams.filter, '') ? { filter: urlSearchParams.filter } : ''),
...(!isDefaultOrMissing(urlSearchParams.includedPolicies, '')
? { includedPolicies: urlSearchParams.includedPolicies }
: ''),
};
};

export const extractArtifactListPageUrlSearchParams = (
query: querystring.ParsedUrlQuery
): ArtifactListPageUrlParams => {
const showParamValue = extractFirstParamValue(query, 'show') as ArtifactListPageUrlParams['show'];

return {
page: extractPageNumber(query),
pageSize: extractPageSizeNumber(query),
includedPolicies: extractFirstParamValue(query, 'includedPolicies'),
show:
showParamValue && SHOW_PARAM_ALLOWED_VALUES.includes(showParamValue)
? showParamValue
: undefined,
itemId: extractFirstParamValue(query, 'itemId'),
};
};
Comment on lines +31 to +65
Copy link
Member

Choose a reason for hiding this comment

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

Nice ⭐ Looks like I had the same idea in my PR here.

Copy link
Member

Choose a reason for hiding this comment

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

I think I'll copy these changes into my own PR or wait for this to merge. After updating to use ArtifactListPageUrlParams for types some of these helper methods become obsolete.


export const getArtifactListPageUrlPath = (
/** The path to the desired page that is using the `<ArtifactListPage>` component */
path: string,
/** An optional set of url search params. These will be normalized prior to being appended to the url path */
searchParams: Partial<ArtifactListPageUrlParams> = {}
): string => {
return `${path}${appendSearch(
querystring.stringify(normalizeArtifactListPageUrlSearchParams(searchParams))
)}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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.
*/

export * from './utils';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI: This directory (url_routing) will be refactored and consolidated into a folder named routing that will contain multiple files. I did not want to do that in this PR because then I would have to move over the existing routing.ts module and that would have pulled in several others due to path changes. I will create a follow up PR to this one.

export {
getArtifactListPageUrlPath,
extractArtifactListPageUrlSearchParams,
} from './artifact_list_page_routing';
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some of these were copied over from the existing routing.ts. Once I refactor the existing routing.ts over to a new folder named routing, this util.ts module will likely hold all of the other utilities found there


// eslint-disable-next-line import/no-nodejs-modules
import querystring from 'querystring';
import { MANAGEMENT_DEFAULT_PAGE_SIZE, MANAGEMENT_PAGE_SIZE_OPTIONS } from '../constants';

/**
* Checks if a given value is either undefined or equal to the default value provided on input
* @param value
* @param defaultValue
*/
export const isDefaultOrMissing = <T>(value: T | undefined, defaultValue: T) => {
return value === undefined || value === defaultValue;
};

/**
* Given an object with url params, and a given key, return back only the first param value (case multiples were defined)
* @param query
* @param key
*/
export const extractFirstParamValue = (
query: querystring.ParsedUrlQuery,
key: string
): string | undefined => {
const value = query[key];

return Array.isArray(value) ? value[value.length - 1] : value;
};

/**
* Extracts the page number from a url query object `page` param and validates it.
* @param query
*/
export const extractPageNumber = (query: querystring.ParsedUrlQuery): number => {
const pageIndex = Number(extractFirstParamValue(query, 'page'));

return !Number.isFinite(pageIndex) || pageIndex < 1 ? 1 : pageIndex;
};

/**
* Extracts the page size from a url query object `pageSize` param and validates it
* @param query
*/
export const extractPageSizeNumber = (query: querystring.ParsedUrlQuery): number => {
const pageSize = Number(extractFirstParamValue(query, 'pageSize'));

return MANAGEMENT_PAGE_SIZE_OPTIONS.includes(pageSize) ? pageSize : MANAGEMENT_DEFAULT_PAGE_SIZE;
};
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ describe('When using the ArtifactListPage component', () => {
});

describe('and data exists', () => {
let renderWithListData: () => Promise<ReturnType<typeof render>>;
let renderWithListData: (
props?: Partial<ArtifactListPageProps>
) => Promise<ReturnType<typeof render>>;

beforeEach(async () => {
renderWithListData = async () => {
render();
renderWithListData = async (props) => {
render(props);

await act(async () => {
await waitFor(() => {
Expand Down Expand Up @@ -159,6 +161,35 @@ describe('When using the ArtifactListPage component', () => {

expect(getByTestId('testPage-deleteModal')).toBeTruthy();
});

it.each([
['create button', 'testPage-pageAddButton', { allowCardCreateAction: false }],
['edit card action', 'testPage-card-cardEditAction', { allowCardEditAction: false }],
['delete card action', 'testPage-card-cardDeleteAction', { allowCardDeleteAction: false }],
])('should hide the %s', async (_, testId, renderProps) => {
const { queryByTestId } = await renderWithListData(
renderProps as Partial<ArtifactListPageProps>
);
await getFirstCard({ showActions: true });

expect(queryByTestId(testId)).toBeNull();
});

it.each([
['create', 'show=create'],
['edit', 'show=edit&itemId=123'],
])(
'should NOT show flyout if url has a show param of %s but the action is not allowed',
async (_, urlParam) => {
history.push(`somepage?${urlParam}`);
const { queryByTestId } = await renderWithListData({
allowCardCreateAction: false,
allowCardEditAction: false,
});

expect(queryByTestId('testPage-flyout')).toBeNull();
}
);
});

describe('and search bar is used', () => {
Expand Down
Loading