mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] [Endpoint] Add back button as a secondary action in empty state (#122769)
* Adds back button as secondary action in empty state * Make back button persistent when pushing new history and added unit tests for it * Fix typo and removed unnecessary return * Remove unnecessary return statements * Move repeated code into a generic hook
This commit is contained in:
parent
0e93af0aa7
commit
9257696c3d
18 changed files with 259 additions and 85 deletions
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { useState, useEffect } from 'react';
|
||||
import { ListPageRouteState } from '../../../common/endpoint/types';
|
||||
|
||||
export function useMemoizedRouteState(routeState: ListPageRouteState | undefined) {
|
||||
const [memoizedRouteState, setMemoizedRouteState] = useState<ListPageRouteState | undefined>();
|
||||
useEffect(() => {
|
||||
// At some point we would like to check if the path has changed or not to keep this consistent across different pages
|
||||
if (routeState && routeState.onBackButtonNavigateTo) {
|
||||
setMemoizedRouteState(routeState);
|
||||
}
|
||||
}, [routeState]);
|
||||
|
||||
return memoizedRouteState;
|
||||
}
|
|
@ -63,15 +63,9 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp
|
|||
|
||||
const getTestId = useTestIdGenerator(otherProps['data-test-subj']);
|
||||
|
||||
const pageHeader = useMemo(
|
||||
() =>
|
||||
hideHeader ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" alignItems="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
{headerBackComponent && <>{headerBackComponent}</>}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
return (
|
||||
<div {...otherProps}>
|
||||
{!hideHeader && (
|
||||
<>
|
||||
<EuiPageHeader
|
||||
pageTitle={header}
|
||||
|
@ -83,22 +77,7 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp
|
|||
/>
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
),
|
||||
[
|
||||
actions,
|
||||
description,
|
||||
getTestId,
|
||||
hasBottomBorder,
|
||||
header,
|
||||
headerBackComponent,
|
||||
hideHeader,
|
||||
restrictWidth,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<div {...otherProps}>
|
||||
{pageHeader}
|
||||
)}
|
||||
|
||||
<EuiPageContent
|
||||
hasBorder={false}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 React, { memo } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { CommonProps, EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
import { ListPageRouteState } from '../../../../common/endpoint/types';
|
||||
|
||||
import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
|
||||
|
||||
export type BackToExternalAppSecondaryButtonProps = CommonProps & ListPageRouteState;
|
||||
export const BackToExternalAppSecondaryButton = memo<BackToExternalAppSecondaryButtonProps>(
|
||||
({ backButtonLabel, backButtonUrl, onBackButtonNavigateTo, ...commonProps }) => {
|
||||
const handleBackOnClick = useNavigateToAppEventHandler(...onBackButtonNavigateTo);
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @elastic/eui/href-or-on-click
|
||||
<EuiButtonEmpty
|
||||
{...commonProps}
|
||||
data-test-subj="backToOrigin"
|
||||
size="s"
|
||||
href={backButtonUrl}
|
||||
onClick={handleBackOnClick}
|
||||
textProps={{ className: 'text' }}
|
||||
>
|
||||
{backButtonLabel || (
|
||||
<FormattedMessage id="xpack.securitySolution.list.backButton" defaultMessage="Back" />
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
BackToExternalAppSecondaryButton.displayName = 'BackToExternalAppSecondaryButton';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { BackToExternalAppSecondaryButton } from './back_to_external_app_secondary_button';
|
|
@ -13,7 +13,7 @@ export const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
|||
min-height: calc(100vh - 140px);
|
||||
`;
|
||||
|
||||
export const ManagementEmptyStateWraper = memo(({ children }) => {
|
||||
export const ManagementEmptyStateWrapper = memo(({ children }) => {
|
||||
return (
|
||||
<StyledEuiFlexGroup direction="column" alignItems="center">
|
||||
<EuiPageTemplate template="centeredContent">{children}</EuiPageTemplate>
|
||||
|
@ -21,4 +21,4 @@ export const ManagementEmptyStateWraper = memo(({ children }) => {
|
|||
);
|
||||
});
|
||||
|
||||
ManagementEmptyStateWraper.displayName = 'ManagementEmptyStateWraper';
|
||||
ManagementEmptyStateWrapper.displayName = 'ManagementEmptyStateWrapper';
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { ManagementEmptyStateWraper } from './management_empty_state_wraper';
|
||||
import { ManagementEmptyStateWrapper } from './management_empty_state_wrapper';
|
||||
|
||||
export const ManagementPageLoader = memo<{ 'data-test-subj': string }>(
|
||||
({ 'data-test-subj': dataTestSubj }) => {
|
||||
return (
|
||||
<ManagementEmptyStateWraper>
|
||||
<ManagementEmptyStateWrapper>
|
||||
<EuiLoadingSpinner data-test-subj={dataTestSubj} size="l" />
|
||||
</ManagementEmptyStateWraper>
|
||||
</ManagementEmptyStateWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { memo } from 'react';
|
|||
import styled, { css } from 'styled-components';
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ManagementEmptyStateWraper } from '../../../../../components/management_empty_state_wraper';
|
||||
import { ManagementEmptyStateWrapper } from '../../../../../components/management_empty_state_wrapper';
|
||||
|
||||
const EmptyPrompt = styled(EuiEmptyPrompt)`
|
||||
${() => css`
|
||||
|
@ -21,9 +21,10 @@ export const EventFiltersListEmptyState = memo<{
|
|||
onAdd: () => void;
|
||||
/** Should the Add button be disabled */
|
||||
isAddDisabled?: boolean;
|
||||
}>(({ onAdd, isAddDisabled = false }) => {
|
||||
backComponent?: React.ReactNode;
|
||||
}>(({ onAdd, isAddDisabled = false, backComponent }) => {
|
||||
return (
|
||||
<ManagementEmptyStateWraper>
|
||||
<ManagementEmptyStateWrapper>
|
||||
<EmptyPrompt
|
||||
data-test-subj="eventFiltersEmpty"
|
||||
iconType="plusInCircle"
|
||||
|
@ -41,7 +42,7 @@ export const EventFiltersListEmptyState = memo<{
|
|||
defaultMessage="Add an event filter to exclude high volume or unwanted events from being written to Elasticsearch."
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
actions={[
|
||||
<EuiButton
|
||||
fill
|
||||
isDisabled={isAddDisabled}
|
||||
|
@ -52,10 +53,11 @@ export const EventFiltersListEmptyState = memo<{
|
|||
id="xpack.securitySolution.eventFilters.listEmpty.addButton"
|
||||
defaultMessage="Add event filter"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
</EuiButton>,
|
||||
...(backComponent ? [backComponent] : []),
|
||||
]}
|
||||
/>
|
||||
</ManagementEmptyStateWraper>
|
||||
</ManagementEmptyStateWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -221,11 +221,27 @@ describe('When on the Event Filters List Page', () => {
|
|||
expect(button).toHaveAttribute('href', '/fleet');
|
||||
});
|
||||
|
||||
it('back button is not present', () => {
|
||||
it('back button is still present after push history', () => {
|
||||
act(() => {
|
||||
history.push('/administration/event_filters');
|
||||
});
|
||||
expect(renderResult.queryByTestId('backToOrigin')).toBeNull();
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).not.toBeNull();
|
||||
expect(button).toHaveAttribute('href', '/fleet');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the back button is not present', () => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render();
|
||||
act(() => {
|
||||
history.push('/administration/event_filters');
|
||||
});
|
||||
});
|
||||
|
||||
it('back button is not present when missing history params', () => {
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,6 +45,7 @@ import {
|
|||
import { EventFilterDeleteModal } from './components/event_filter_delete_modal';
|
||||
|
||||
import { SearchExceptions } from '../../../components/search_exceptions';
|
||||
import { BackToExternalAppSecondaryButton } from '../../../components/back_to_external_app_secondary_button';
|
||||
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
|
||||
import { ABOUT_EVENT_FILTERS } from './translations';
|
||||
import { useGetEndpointSpecificPolicies } from '../../../services/policies/hooks';
|
||||
|
@ -52,6 +53,7 @@ import { useToasts } from '../../../../common/lib/kibana';
|
|||
import { getLoadPoliciesError } from '../../../common/translations';
|
||||
import { useEndpointPoliciesToArtifactPolicies } from '../../../components/artifact_entry_card/hooks/use_endpoint_policies_to_artifact_policies';
|
||||
import { ManagementPageLoader } from '../../../components/management_page_loader';
|
||||
import { useMemoizedRouteState } from '../../../common/hooks';
|
||||
|
||||
type ArtifactEntryCardType = typeof ArtifactEntryCard;
|
||||
|
||||
|
@ -103,6 +105,20 @@ export const EventFiltersListPage = memo(() => {
|
|||
const navigateCallback = useEventFiltersNavigateCallback();
|
||||
const showFlyout = !!location.show;
|
||||
|
||||
const memoizedRouteState = useMemoizedRouteState(routeState);
|
||||
|
||||
const backButtonEmptyComponent = useMemo(() => {
|
||||
if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppSecondaryButton {...memoizedRouteState} />;
|
||||
}
|
||||
}, [memoizedRouteState]);
|
||||
|
||||
const backButtonHeaderComponent = useMemo(() => {
|
||||
if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppButton {...memoizedRouteState} />;
|
||||
}
|
||||
}, [memoizedRouteState]);
|
||||
|
||||
// load the list of policies
|
||||
const policiesRequest = useGetEndpointSpecificPolicies({
|
||||
onError: (err) => {
|
||||
|
@ -141,13 +157,6 @@ export const EventFiltersListPage = memo(() => {
|
|||
}
|
||||
}, [dispatch, formEntry, history, isActionError, location, navigateCallback]);
|
||||
|
||||
const backButton = useMemo(() => {
|
||||
if (routeState && routeState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppButton {...routeState} />;
|
||||
}
|
||||
return null;
|
||||
}, [routeState]);
|
||||
|
||||
const handleAddButtonClick = useCallback(
|
||||
() =>
|
||||
navigateCallback({
|
||||
|
@ -240,7 +249,7 @@ export const EventFiltersListPage = memo(() => {
|
|||
|
||||
return (
|
||||
<AdministrationListPage
|
||||
headerBackComponent={backButton}
|
||||
headerBackComponent={backButtonHeaderComponent}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.eventFilters.list.pageTitle"
|
||||
|
@ -312,7 +321,11 @@ export const EventFiltersListPage = memo(() => {
|
|||
data-test-subj="eventFiltersContent"
|
||||
noItemsMessage={
|
||||
!doesDataExist && (
|
||||
<EventFiltersListEmptyState onAdd={handleAddButtonClick} isAddDisabled={showFlyout} />
|
||||
<EventFiltersListEmptyState
|
||||
onAdd={handleAddButtonClick}
|
||||
isAddDisabled={showFlyout}
|
||||
backComponent={backButtonEmptyComponent}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { memo } from 'react';
|
|||
import styled, { css } from 'styled-components';
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ManagementEmptyStateWraper } from '../../../../components/management_empty_state_wraper';
|
||||
import { ManagementEmptyStateWrapper } from '../../../../components/management_empty_state_wrapper';
|
||||
|
||||
const EmptyPrompt = styled(EuiEmptyPrompt)`
|
||||
${() => css`
|
||||
|
@ -17,9 +17,12 @@ const EmptyPrompt = styled(EuiEmptyPrompt)`
|
|||
`}
|
||||
`;
|
||||
|
||||
export const HostIsolationExceptionsEmptyState = memo<{ onAdd: () => void }>(({ onAdd }) => {
|
||||
export const HostIsolationExceptionsEmptyState = memo<{
|
||||
onAdd: () => void;
|
||||
backComponent?: React.ReactNode;
|
||||
}>(({ onAdd, backComponent }) => {
|
||||
return (
|
||||
<ManagementEmptyStateWraper>
|
||||
<ManagementEmptyStateWrapper>
|
||||
<EmptyPrompt
|
||||
data-test-subj="hostIsolationExceptionsEmpty"
|
||||
iconType="plusInCircle"
|
||||
|
@ -37,7 +40,7 @@ export const HostIsolationExceptionsEmptyState = memo<{ onAdd: () => void }>(({
|
|||
defaultMessage="Add a Host isolation exception to allow isolated hosts to communicate with specific IPs."
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
actions={[
|
||||
<EuiButton
|
||||
fill
|
||||
onClick={onAdd}
|
||||
|
@ -47,10 +50,12 @@ export const HostIsolationExceptionsEmptyState = memo<{ onAdd: () => void }>(({
|
|||
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.addButton"
|
||||
defaultMessage="Add Host isolation exception"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
</EuiButton>,
|
||||
|
||||
...(backComponent ? [backComponent] : []),
|
||||
]}
|
||||
/>
|
||||
</ManagementEmptyStateWraper>
|
||||
</ManagementEmptyStateWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -265,5 +265,47 @@ describe('When on the host isolation exceptions page', () => {
|
|||
expect(renderResult.queryByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the back button is present', () => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render();
|
||||
act(() => {
|
||||
history.push(HOST_ISOLATION_EXCEPTIONS_PATH, {
|
||||
onBackButtonNavigateTo: [{ appId: 'appId' }],
|
||||
backButtonLabel: 'back to fleet',
|
||||
backButtonUrl: '/fleet',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('back button is present', () => {
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).not.toBeNull();
|
||||
expect(button).toHaveAttribute('href', '/fleet');
|
||||
});
|
||||
|
||||
it('back button is still present after push history', () => {
|
||||
act(() => {
|
||||
history.push(HOST_ISOLATION_EXCEPTIONS_PATH);
|
||||
});
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).not.toBeNull();
|
||||
expect(button).toHaveAttribute('href', '/fleet');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the back button is not present', () => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render();
|
||||
act(() => {
|
||||
history.push(HOST_ISOLATION_EXCEPTIONS_PATH);
|
||||
});
|
||||
});
|
||||
|
||||
it('back button is not present when missing history params', () => {
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ import { getLoadPoliciesError } from '../../../common/translations';
|
|||
import { AdministrationListPage } from '../../../components/administration_list_page';
|
||||
import { ArtifactEntryCard, ArtifactEntryCardProps } from '../../../components/artifact_entry_card';
|
||||
import { useEndpointPoliciesToArtifactPolicies } from '../../../components/artifact_entry_card/hooks/use_endpoint_policies_to_artifact_policies';
|
||||
import { BackToExternalAppSecondaryButton } from '../../../components/back_to_external_app_secondary_button';
|
||||
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
|
||||
import { ManagementPageLoader } from '../../../components/management_page_loader';
|
||||
import { PaginatedContent, PaginatedContentProps } from '../../../components/paginated_content';
|
||||
|
@ -42,6 +43,7 @@ import {
|
|||
useHostIsolationExceptionsNavigateCallback,
|
||||
useHostIsolationExceptionsSelector,
|
||||
} from './hooks';
|
||||
import { useMemoizedRouteState } from '../../../common/hooks';
|
||||
|
||||
type HostIsolationExceptionPaginatedContent = PaginatedContentProps<
|
||||
Immutable<ExceptionListItemSchema>,
|
||||
|
@ -56,6 +58,20 @@ export const HostIsolationExceptionsList = () => {
|
|||
const location = useHostIsolationExceptionsSelector(getCurrentLocation);
|
||||
const navigateCallback = useHostIsolationExceptionsNavigateCallback();
|
||||
|
||||
const memoizedRouteState = useMemoizedRouteState(routeState);
|
||||
|
||||
const backButtonEmptyComponent = useMemo(() => {
|
||||
if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppSecondaryButton {...memoizedRouteState} />;
|
||||
}
|
||||
}, [memoizedRouteState]);
|
||||
|
||||
const backButtonHeaderComponent = useMemo(() => {
|
||||
if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppButton {...memoizedRouteState} />;
|
||||
}
|
||||
}, [memoizedRouteState]);
|
||||
|
||||
const [itemToDelete, setItemToDelete] = useState<ExceptionListItemSchema | null>(null);
|
||||
|
||||
const includedPoliciesParam = location.included_policies;
|
||||
|
@ -155,13 +171,6 @@ export const HostIsolationExceptionsList = () => {
|
|||
[navigateCallback]
|
||||
);
|
||||
|
||||
const backButton = useMemo(() => {
|
||||
if (routeState && routeState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppButton {...routeState} />;
|
||||
}
|
||||
return null;
|
||||
}, [routeState]);
|
||||
|
||||
const handleAddButtonClick = useCallback(
|
||||
() =>
|
||||
navigateCallback({
|
||||
|
@ -193,7 +202,7 @@ export const HostIsolationExceptionsList = () => {
|
|||
|
||||
return (
|
||||
<AdministrationListPage
|
||||
headerBackComponent={backButton}
|
||||
headerBackComponent={backButtonHeaderComponent}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hostIsolationExceptions.list.pageTitle"
|
||||
|
@ -272,7 +281,12 @@ export const HostIsolationExceptionsList = () => {
|
|||
contentClassName="host-isolation-exceptions-container"
|
||||
data-test-subj="hostIsolationExceptionsContent"
|
||||
noItemsMessage={
|
||||
!hasDataToShow && <HostIsolationExceptionsEmptyState onAdd={handleAddButtonClick} />
|
||||
!hasDataToShow && (
|
||||
<HostIsolationExceptionsEmptyState
|
||||
onAdd={handleAddButtonClick}
|
||||
backComponent={backButtonEmptyComponent}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</AdministrationListPage>
|
||||
|
|
|
@ -72,7 +72,7 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(
|
|||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
|
||||
{ defaultMessage: 'Back to Endpoint Integration' }
|
||||
{ defaultMessage: 'Return to Endpoint Security integrations' }
|
||||
),
|
||||
onBackButtonNavigateTo: [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
|
|
|
@ -73,7 +73,7 @@ export const FleetHostIsolationExceptionsCard = memo<PackageCustomExtensionCompo
|
|||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.hostIsolationExceptionsSummary.backButtonLabel',
|
||||
{ defaultMessage: 'Back to Endpoint Integration' }
|
||||
{ defaultMessage: 'Return to Endpoint Security integrations' }
|
||||
),
|
||||
onBackButtonNavigateTo: [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
|
|
|
@ -33,7 +33,7 @@ export const FleetTrustedAppsCardWrapper = memo<PackageCustomExtensionComponentP
|
|||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
|
||||
{ defaultMessage: 'Back to Endpoint Integration' }
|
||||
{ defaultMessage: 'Return to Endpoint Security integrations' }
|
||||
),
|
||||
onBackButtonNavigateTo: [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
|
|
|
@ -8,15 +8,16 @@
|
|||
import React, { memo } from 'react';
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ManagementEmptyStateWraper } from '../../../../components/management_empty_state_wraper';
|
||||
import { ManagementEmptyStateWrapper } from '../../../../components/management_empty_state_wrapper';
|
||||
|
||||
export const EmptyState = memo<{
|
||||
onAdd: () => void;
|
||||
/** Should the Add button be disabled */
|
||||
isAddDisabled?: boolean;
|
||||
}>(({ onAdd, isAddDisabled = false }) => {
|
||||
backComponent?: React.ReactNode;
|
||||
}>(({ onAdd, isAddDisabled = false, backComponent }) => {
|
||||
return (
|
||||
<ManagementEmptyStateWraper>
|
||||
<ManagementEmptyStateWrapper>
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="trustedAppEmptyState"
|
||||
iconType="plusInCircle"
|
||||
|
@ -34,7 +35,7 @@ export const EmptyState = memo<{
|
|||
defaultMessage="Add a trusted application to improve performance or alleviate conflicts with other applications running on your hosts."
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
actions={[
|
||||
<EuiButton
|
||||
fill
|
||||
isDisabled={isAddDisabled}
|
||||
|
@ -45,10 +46,11 @@ export const EmptyState = memo<{
|
|||
id="xpack.securitySolution.trustedapps.list.addButton"
|
||||
defaultMessage="Add trusted application"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
</EuiButton>,
|
||||
...(backComponent ? [backComponent] : []),
|
||||
]}
|
||||
/>
|
||||
</ManagementEmptyStateWraper>
|
||||
</ManagementEmptyStateWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -858,11 +858,31 @@ describe('When on the Trusted Apps Page', () => {
|
|||
expect(button).toHaveAttribute('href', '/fleet');
|
||||
});
|
||||
|
||||
it('back button is not present', () => {
|
||||
it('back button is present after push history', () => {
|
||||
reactTestingLibrary.act(() => {
|
||||
history.push('/administration/trusted_apps');
|
||||
});
|
||||
expect(renderResult.queryByTestId('backToOrigin')).toBeNull();
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).not.toBeNull();
|
||||
expect(button).toHaveAttribute('href', '/fleet');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the back button is not present', () => {
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
beforeEach(async () => {
|
||||
renderResult = render();
|
||||
await act(async () => {
|
||||
await waitForAction('trustedAppsListResourceStateChanged');
|
||||
});
|
||||
reactTestingLibrary.act(() => {
|
||||
history.push('/administration/trusted_apps');
|
||||
});
|
||||
});
|
||||
|
||||
it('back button is not present when missing history params', () => {
|
||||
const button = renderResult.queryByTestId('backToOrigin');
|
||||
expect(button).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,9 +31,11 @@ import { AppAction } from '../../../../common/store/actions';
|
|||
import { ABOUT_TRUSTED_APPS, SEARCH_TRUSTED_APP_PLACEHOLDER } from './translations';
|
||||
import { EmptyState } from './components/empty_state';
|
||||
import { SearchExceptions } from '../../../components/search_exceptions';
|
||||
import { BackToExternalAppSecondaryButton } from '../../../components/back_to_external_app_secondary_button';
|
||||
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
|
||||
import { ListPageRouteState } from '../../../../../common/endpoint/types';
|
||||
import { ManagementPageLoader } from '../../../components/management_page_loader';
|
||||
import { useMemoizedRouteState } from '../../../common/hooks';
|
||||
|
||||
export const TrustedAppsPage = memo(() => {
|
||||
const dispatch = useDispatch<Dispatch<AppAction>>();
|
||||
|
@ -51,6 +53,20 @@ export const TrustedAppsPage = memo(() => {
|
|||
})
|
||||
);
|
||||
|
||||
const memoizedRouteState = useMemoizedRouteState(routeState);
|
||||
|
||||
const backButtonEmptyComponent = useMemo(() => {
|
||||
if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppSecondaryButton {...memoizedRouteState} />;
|
||||
}
|
||||
}, [memoizedRouteState]);
|
||||
|
||||
const backButtonHeaderComponent = useMemo(() => {
|
||||
if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppButton {...memoizedRouteState} />;
|
||||
}
|
||||
}, [memoizedRouteState]);
|
||||
|
||||
const handleAddButtonClick = useTrustedAppsNavigateCallback(() => ({
|
||||
show: 'create',
|
||||
id: undefined,
|
||||
|
@ -75,13 +91,6 @@ export const TrustedAppsPage = memo(() => {
|
|||
[didEntriesExist, doEntriesExist, isCheckingIfEntriesExists]
|
||||
);
|
||||
|
||||
const backButton = useMemo(() => {
|
||||
if (routeState && routeState.onBackButtonNavigateTo) {
|
||||
return <BackToExternalAppButton {...routeState} />;
|
||||
}
|
||||
return null;
|
||||
}, [routeState]);
|
||||
|
||||
const addButton = (
|
||||
<EuiButton
|
||||
fill
|
||||
|
@ -142,7 +151,11 @@ export const TrustedAppsPage = memo(() => {
|
|||
</EuiFlexGroup>
|
||||
</>
|
||||
) : (
|
||||
<EmptyState onAdd={handleAddButtonClick} isAddDisabled={showCreateFlyout} />
|
||||
<EmptyState
|
||||
onAdd={handleAddButtonClick}
|
||||
isAddDisabled={showCreateFlyout}
|
||||
backComponent={backButtonEmptyComponent}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -150,13 +163,13 @@ export const TrustedAppsPage = memo(() => {
|
|||
return (
|
||||
<AdministrationListPage
|
||||
data-test-subj="trustedAppsListPage"
|
||||
headerBackComponent={backButtonHeaderComponent}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.trustedapps.list.pageTitle"
|
||||
defaultMessage="Trusted applications"
|
||||
/>
|
||||
}
|
||||
headerBackComponent={backButton}
|
||||
subtitle={ABOUT_TRUSTED_APPS}
|
||||
actions={addButton}
|
||||
hideHeader={!canDisplayContent()}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue