Skip to content

Commit

Permalink
[Security Solution][Endpoint][Admin][Policy] Policy List table (#123760)
Browse files Browse the repository at this point in the history
  • Loading branch information
parkiino authored Feb 23, 2022
1 parent ef557af commit 9e8d88e
Show file tree
Hide file tree
Showing 13 changed files with 554 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@

import React, { memo, useMemo } from 'react';
import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui';
import { useEndpointSelector } from '../hooks';
import { nonExistingPolicies } from '../../store/selectors';
import { getPolicyDetailPath } from '../../../../common/routing';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
import { getPolicyDetailPath } from '../common/routing';
import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { useAppUrl } from '../../common/lib/kibana/hooks';

/**
* A policy link (to details) that first checks to see if the policy id exists against
Expand All @@ -21,9 +19,9 @@ import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
export const EndpointPolicyLink = memo<
Omit<EuiLinkAnchorProps, 'href'> & {
policyId: string;
missingPolicies?: Record<string, boolean>;
}
>(({ policyId, children, onClick, ...otherProps }) => {
const missingPolicies = useEndpointSelector(nonExistingPolicies);
>(({ policyId, children, onClick, missingPolicies = {}, ...otherProps }) => {
const { getAppUrl } = useAppUrl();
const { toRoutePath, toRouteUrl } = useMemo(() => {
const path = getPolicyDetailPath(policyId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 { useCallback, useEffect, useMemo, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { MANAGEMENT_DEFAULT_PAGE_SIZE, MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../common/constants';
import { useUrlParams } from './use_url_params';

// Page is not index-based, it is 1-based
interface Pagination {
page: number;
pageSize: number;
}

type SetUrlPagination = (pagination: Pagination) => void;
interface UrlPagination {
pagination: Pagination;
setPagination: SetUrlPagination;
pageSizeOptions: number[];
}

type UrlPaginationParams = Partial<Pagination>;

const paginationFromUrlParams = (urlParams: UrlPaginationParams): Pagination => {
const pagination: Pagination = {
pageSize: MANAGEMENT_DEFAULT_PAGE_SIZE,
page: 1,
};

// Search params can appear multiple times in the URL, in which case the value for them,
// once parsed, would be an array. In these case, we take the last value defined
pagination.page = Number(
(Array.isArray(urlParams.page) ? urlParams.page[urlParams.page.length - 1] : urlParams.page) ??
pagination.page
);
pagination.pageSize =
Number(
(Array.isArray(urlParams.pageSize)
? urlParams.pageSize[urlParams.pageSize.length - 1]
: urlParams.pageSize) ?? pagination.pageSize
) ?? pagination.pageSize;

// If Current Page is not a valid positive integer, set it to 1
if (!Number.isFinite(pagination.page) || pagination.page < 1) {
pagination.page = 1;
}

// if pageSize is not one of the expected page sizes, reset it to 10 (default)
if (!MANAGEMENT_PAGE_SIZE_OPTIONS.includes(pagination.pageSize)) {
pagination.pageSize = MANAGEMENT_DEFAULT_PAGE_SIZE;
}

return pagination;
};

/**
* Uses URL params for pagination and also persists those to the URL as they are updated
*/
export const useUrlPagination = (): UrlPagination => {
const location = useLocation();
const history = useHistory();
const { urlParams, toUrlParams } = useUrlParams();
const urlPaginationParams = useMemo(() => {
return paginationFromUrlParams(urlParams);
}, [urlParams]);
const [pagination, setPagination] = useState<Pagination>(urlPaginationParams);
const setUrlPagination = useCallback<SetUrlPagination>(
({ pageSize, page }) => {
history.push({
...location,
search: toUrlParams({
...urlParams,
page,
pageSize,
}),
});
},
[history, location, toUrlParams, urlParams]
);

useEffect(() => {
setPagination((prevState) => {
return {
...prevState,
...paginationFromUrlParams(urlParams),
};
});
}, [setPagination, urlParams]);

return {
pagination,
setPagination: setUrlPagination,
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { useMemo } from 'react';
import { parse, stringify, ParsedQuery } from 'query-string';
import { useLocation } from 'react-router-dom';

/**
* Parses `search` params and returns an object with them along with a `toUrlParams` function
* that allows being able to retrieve a stringified version of an object (default is the
* `urlParams` that was parsed) for use in the url.
* Object will be recreated every time `search` changes.
*/
export function useUrlParams(): {
urlParams: ParsedQuery<string>;
toUrlParams: (params: ParsedQuery<string | number>) => string;
} {
const { search } = useLocation();
return useMemo(() => {
const urlParams = parse(search);
return {
urlParams,
toUrlParams: (params = urlParams) => stringify(params),
};
}, [search]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { isPolicyOutOfDate } from '../../utils';
import { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types';
import { useEndpointSelector } from '../hooks';
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
import { nonExistingPolicies, policyResponseStatus, uiQueryParams } from '../../store/selectors';
import { POLICY_STATUS_TO_BADGE_COLOR } from '../host_constants';
import { FormattedDate } from '../../../../../common/components/formatted_date';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { getEndpointDetailsPath } from '../../../../common/routing';
import { EndpointPolicyLink } from '../components/endpoint_policy_link';
import { EndpointPolicyLink } from '../../../../components/endpoint_policy_link';
import { OutOfDate } from '../components/out_of_date';
import { EndpointAgentStatus } from '../components/endpoint_agent_status';

Expand Down Expand Up @@ -64,6 +64,8 @@ export const EndpointDetailsContent = memo(
policyResponseStatus
) as keyof typeof POLICY_STATUS_TO_BADGE_COLOR;

const missingPolicies = useEndpointSelector(nonExistingPolicies);

const policyResponseRoutePath = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { selected_endpoint, show, ...currentUrlParams } = queryParams;
Expand Down Expand Up @@ -131,6 +133,7 @@ export const EndpointDetailsContent = memo(
policyId={details.Endpoint.policy.applied.id}
data-test-subj="policyDetailsValue"
className={'policyLineText'}
missingPolicies={missingPolicies}
>
{details.Endpoint.policy.applied.name}
</EndpointPolicyLink>
Expand Down Expand Up @@ -211,7 +214,7 @@ export const EndpointDetailsContent = memo(
),
},
];
}, [details, hostStatus, policyStatus, policyStatusClickHandler, policyInfo]);
}, [details, hostStatus, policyStatus, policyStatusClickHandler, policyInfo, missingPolicies]);

return (
<EndpointDetailsContentStyled>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { DEFAULT_POLL_INTERVAL, MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../co
import { PolicyEmptyState, HostsEmptyState } from '../../../components/management_empty_state';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { EndpointPolicyLink } from '../../../components/endpoint_policy_link';
import {
CreatePackagePolicyRouteState,
AgentPolicyDetailsDeployAgentAction,
Expand All @@ -49,7 +50,6 @@ import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/rou
import { useFormatUrl } from '../../../../common/components/link_to';
import { useAppUrl } from '../../../../common/lib/kibana/hooks';
import { EndpointAction } from '../store/action';
import { EndpointPolicyLink } from './components/endpoint_policy_link';
import { OutOfDate } from './components/out_of_date';
import { AdminSearchBar } from './components/search_bar';
import { AdministrationListPage } from '../../../components/administration_list_page';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const EventFiltersFlyout: React.FC<EventFiltersFlyoutProps> = memo(

// load the list of policies>
const policiesRequest = useGetEndpointSpecificPolicies({
perPage: 1000,
onError: (error) => {
toasts.addWarning(getLoadPoliciesError(error));
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const EventFiltersListPage = memo(() => {

// load the list of policies
const policiesRequest = useGetEndpointSpecificPolicies({
perPage: 1000,
onError: (err) => {
toasts.addDanger(getLoadPoliciesError(err));
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface PolicyEventFiltersListProps {
export const PolicyEventFiltersList = React.memo<PolicyEventFiltersListProps>(({ policy }) => {
const { getAppUrl } = useAppUrl();
const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
const policiesRequest = useGetEndpointSpecificPolicies();
const policiesRequest = useGetEndpointSpecificPolicies({ perPage: 1000 });
const navigateCallback = usePolicyDetailsEventFiltersNavigateCallback();
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);
const [expandedItemsMap, setExpandedItemsMap] = useState<Map<string, boolean>>(new Map());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const PolicyHostIsolationExceptionsList = ({
const { state } = useGetLinkTo(policyId, policyName);

// load the list of policies>
const policiesRequest = useGetEndpointSpecificPolicies();
const policiesRequest = useGetEndpointSpecificPolicies({ perPage: 1000 });
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);

const [exceptionItemToDelete, setExceptionItemToDelete] = useState<
Expand Down
Loading

0 comments on commit 9e8d88e

Please sign in to comment.