Skip to content

Commit e94a34d

Browse files
[Security solution][Endpoint] Add back button when to the event filters list (#101280)
* Add back button when to the event filters list. Isolated back to external app button to be used as generic component * Adds unit tests for back button Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 16e66b8 commit e94a34d

File tree

12 files changed

+151
-67
lines changed

12 files changed

+151
-67
lines changed

x-pack/plugins/security_solution/common/endpoint/types/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,3 +1114,15 @@ export interface GetExceptionSummaryResponse {
11141114
macos: number;
11151115
linux: number;
11161116
}
1117+
1118+
/**
1119+
* Supported React-Router state for the Generic List page
1120+
*/
1121+
export interface ListPageRouteState {
1122+
/** Where the user should be redirected to when the `Back` button is clicked */
1123+
onBackButtonNavigateTo: Parameters<ApplicationStart['navigateToApp']>;
1124+
/** The URL for the `Back` button */
1125+
backButtonUrl?: string;
1126+
/** The label for the button */
1127+
backButtonLabel?: string;
1128+
}

x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
import { TypeOf } from '@kbn/config-schema';
9-
import { ApplicationStart } from 'kibana/public';
109

1110
import {
1211
DeleteTrustedAppsRequestSchema,
@@ -133,15 +132,3 @@ export type TrustedApp = NewTrustedApp & {
133132
updated_at: string;
134133
updated_by: string;
135134
};
136-
137-
/**
138-
* Supported React-Router state for the Trusted Apps List page
139-
*/
140-
export interface TrustedAppsListPageRouteState {
141-
/** Where the user should be redirected to when the `Back` button is clicked */
142-
onBackButtonNavigateTo: Parameters<ApplicationStart['navigateToApp']>;
143-
/** The URL for the `Back` button */
144-
backButtonUrl?: string;
145-
/** The label for the button */
146-
backButtonLabel?: string;
147-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
import React, { memo } from 'react';
8+
9+
import { FormattedMessage } from '@kbn/i18n/react';
10+
import { EuiButtonEmpty } from '@elastic/eui';
11+
import styled from 'styled-components';
12+
13+
import { ListPageRouteState } from '../../../../common/endpoint/types';
14+
15+
import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
16+
17+
const EuiButtonEmptyStyled = styled(EuiButtonEmpty)`
18+
margin-bottom: ${({ theme }) => theme.eui.euiSizeS};
19+
20+
.euiIcon {
21+
width: ${({ theme }) => theme.eui.euiIconSizes.small};
22+
height: ${({ theme }) => theme.eui.euiIconSizes.small};
23+
}
24+
25+
.text {
26+
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
27+
}
28+
`;
29+
30+
export const BackToExternalAppButton = memo<ListPageRouteState>(
31+
({ backButtonLabel, backButtonUrl, onBackButtonNavigateTo }) => {
32+
const handleBackOnClick = useNavigateToAppEventHandler(...onBackButtonNavigateTo!);
33+
34+
return (
35+
<EuiButtonEmptyStyled
36+
flush="left"
37+
size="xs"
38+
iconType="arrowLeft"
39+
href={backButtonUrl!}
40+
onClick={handleBackOnClick}
41+
textProps={{ className: 'text' }}
42+
data-test-subj="backToOrigin"
43+
>
44+
{backButtonLabel || (
45+
<FormattedMessage id="xpack.securitySolution.list.backButton" defaultMessage="Back" />
46+
)}
47+
</EuiButtonEmptyStyled>
48+
);
49+
}
50+
);
51+
52+
BackToExternalAppButton.displayName = 'BackToExternalAppButton';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export { BackToExternalAppButton } from './back_to_external_app_button';

x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,30 @@ describe('When on the Event Filters List Page', () => {
175175
});
176176
});
177177
});
178+
179+
describe('and the back button is present', () => {
180+
beforeEach(async () => {
181+
renderResult = render();
182+
act(() => {
183+
history.push('/event_filters', {
184+
onBackButtonNavigateTo: [{ appId: 'appId' }],
185+
backButtonLabel: 'back to fleet',
186+
backButtonUrl: '/fleet',
187+
});
188+
});
189+
});
190+
191+
it('back button is present', () => {
192+
const button = renderResult.queryByTestId('backToOrigin');
193+
expect(button).not.toBeNull();
194+
expect(button).toHaveAttribute('href', '/fleet');
195+
});
196+
197+
it('back button is not present', () => {
198+
act(() => {
199+
history.push('/event_filters');
200+
});
201+
expect(renderResult.queryByTestId('backToOrigin')).toBeNull();
202+
});
203+
});
178204
});

x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* 2.0.
66
*/
77

8-
import React, { memo, useCallback, useEffect } from 'react';
8+
import React, { memo, useCallback, useMemo, useEffect } from 'react';
99
import { useDispatch } from 'react-redux';
1010
import { Dispatch } from 'redux';
11-
import { useHistory } from 'react-router-dom';
11+
import { useHistory, useLocation } from 'react-router-dom';
1212
import { i18n } from '@kbn/i18n';
1313
import { FormattedMessage } from '@kbn/i18n/react';
1414
import { EuiButton, EuiSpacer, EuiHorizontalRule, EuiText } from '@elastic/eui';
@@ -34,14 +34,15 @@ import {
3434
showDeleteModal,
3535
} from '../store/selector';
3636
import { PaginatedContent, PaginatedContentProps } from '../../../components/paginated_content';
37-
import { Immutable } from '../../../../../common/endpoint/types';
37+
import { Immutable, ListPageRouteState } from '../../../../../common/endpoint/types';
3838
import {
3939
ExceptionItem,
4040
ExceptionItemProps,
4141
} from '../../../../common/components/exceptions/viewer/exception_item';
4242
import { EventFilterDeleteModal } from './components/event_filter_delete_modal';
4343

4444
import { SearchBar } from '../../../components/search_bar';
45+
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
4546

4647
type EventListPaginatedContent = PaginatedContentProps<
4748
Immutable<ExceptionListItemSchema>,
@@ -59,6 +60,7 @@ const AdministrationListPage = styled(_AdministrationListPage)`
5960
`;
6061

6162
export const EventFiltersListPage = memo(() => {
63+
const { state: routeState } = useLocation<ListPageRouteState | undefined>();
6264
const history = useHistory();
6365
const dispatch = useDispatch<Dispatch<AppAction>>();
6466
const isActionError = useEventFiltersSelector(getActionError);
@@ -103,6 +105,13 @@ export const EventFiltersListPage = memo(() => {
103105
}
104106
}, [dispatch, formEntry, history, isActionError, location, navigateCallback]);
105107

108+
const backButton = useMemo(() => {
109+
if (routeState && routeState.onBackButtonNavigateTo) {
110+
return <BackToExternalAppButton {...routeState} />;
111+
}
112+
return null;
113+
}, [routeState]);
114+
106115
const handleAddButtonClick = useCallback(
107116
() =>
108117
navigateCallback({
@@ -173,6 +182,7 @@ export const EventFiltersListPage = memo(() => {
173182
return (
174183
<AdministrationListPage
175184
beta={false}
185+
headerBackComponent={backButton}
176186
title={
177187
<FormattedMessage
178188
id="xpack.securitySolution.eventFilters.list.pageTitle"

x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
} from '../../../../../../../../../fleet/public';
1717
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
1818
import { getEventFiltersListPath } from '../../../../../../common/routing';
19-
import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
19+
import {
20+
GetExceptionSummaryResponse,
21+
ListPageRouteState,
22+
} from '../../../../../../../../common/endpoint/types';
2023
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
2124
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
2225
import { useToasts } from '../../../../../../../common/lib/kibana';
@@ -64,7 +67,7 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(
6467
};
6568
}, [eventFiltersApi, toasts]);
6669

67-
const eventFiltersRouteState = useMemo(() => {
70+
const eventFiltersRouteState = useMemo<ListPageRouteState>(() => {
6871
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
6972
return {
7073
backButtonLabel: i18n.translate(

x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
1818
import { getTrustedAppsListPath } from '../../../../../../common/routing';
1919
import {
20-
TrustedAppsListPageRouteState,
20+
ListPageRouteState,
2121
GetExceptionSummaryResponse,
2222
} from '../../../../../../../../common/endpoint/types';
2323
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
@@ -67,7 +67,7 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>((
6767
}, [toasts, trustedAppsApi]);
6868
const trustedAppsListUrlPath = getTrustedAppsListPath();
6969

70-
const trustedAppRouteState = useMemo<TrustedAppsListPageRouteState>(() => {
70+
const trustedAppRouteState = useMemo<ListPageRouteState>(() => {
7171
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
7272
return {
7373
backButtonLabel: i18n.translate(

x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,4 +900,34 @@ describe('When on the Trusted Apps Page', () => {
900900
});
901901
});
902902
});
903+
904+
describe('and the back button is present', () => {
905+
let renderResult: ReturnType<AppContextTestRender['render']>;
906+
beforeEach(async () => {
907+
renderResult = render();
908+
await act(async () => {
909+
await waitForAction('trustedAppsListResourceStateChanged');
910+
});
911+
reactTestingLibrary.act(() => {
912+
history.push('/trusted_apps', {
913+
onBackButtonNavigateTo: [{ appId: 'appId' }],
914+
backButtonLabel: 'back to fleet',
915+
backButtonUrl: '/fleet',
916+
});
917+
});
918+
});
919+
920+
it('back button is present', () => {
921+
const button = renderResult.queryByTestId('backToOrigin');
922+
expect(button).not.toBeNull();
923+
expect(button).toHaveAttribute('href', '/fleet');
924+
});
925+
926+
it('back button is not present', () => {
927+
reactTestingLibrary.act(() => {
928+
history.push('/trusted_apps');
929+
});
930+
expect(renderResult.queryByTestId('backToOrigin')).toBeNull();
931+
});
932+
});
903933
});

x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77

88
import React, { memo, useMemo } from 'react';
99
import { useLocation } from 'react-router-dom';
10-
import styled from 'styled-components';
1110
import { FormattedMessage } from '@kbn/i18n/react';
1211
import {
1312
EuiButton,
14-
EuiButtonEmpty,
1513
EuiEmptyPrompt,
1614
EuiFlexGroup,
1715
EuiFlexItem,
@@ -35,14 +33,14 @@ import { TrustedAppsGrid } from './components/trusted_apps_grid';
3533
import { TrustedAppsList } from './components/trusted_apps_list';
3634
import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog';
3735
import { TrustedAppsNotifications } from './trusted_apps_notifications';
38-
import { TrustedAppsListPageRouteState } from '../../../../../common/endpoint/types';
39-
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
4036
import { ABOUT_TRUSTED_APPS, SEARCH_TRUSTED_APP_PLACEHOLDER } from './translations';
4137
import { EmptyState } from './components/empty_state';
4238
import { SearchBar } from '../../../components/search_bar';
39+
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
40+
import { ListPageRouteState } from '../../../../../common/endpoint/types';
4341

4442
export const TrustedAppsPage = memo(() => {
45-
const { state: routeState } = useLocation<TrustedAppsListPageRouteState | undefined>();
43+
const { state: routeState } = useLocation<ListPageRouteState | undefined>();
4644
const location = useTrustedAppsSelector(getCurrentLocation);
4745
const totalItemsCount = useTrustedAppsSelector(getListTotalItemsCount);
4846
const isCheckingIfEntriesExists = useTrustedAppsSelector(checkingIfEntriesExist);
@@ -161,43 +159,3 @@ export const TrustedAppsPage = memo(() => {
161159
});
162160

163161
TrustedAppsPage.displayName = 'TrustedAppsPage';
164-
165-
const EuiButtonEmptyStyled = styled(EuiButtonEmpty)`
166-
margin-bottom: ${({ theme }) => theme.eui.euiSizeS};
167-
168-
.euiIcon {
169-
width: ${({ theme }) => theme.eui.euiIconSizes.small};
170-
height: ${({ theme }) => theme.eui.euiIconSizes.small};
171-
}
172-
173-
.text {
174-
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
175-
}
176-
`;
177-
178-
const BackToExternalAppButton = memo<TrustedAppsListPageRouteState>(
179-
({ backButtonLabel, backButtonUrl, onBackButtonNavigateTo }) => {
180-
const handleBackOnClick = useNavigateToAppEventHandler(...onBackButtonNavigateTo!);
181-
182-
return (
183-
<EuiButtonEmptyStyled
184-
flush="left"
185-
size="xs"
186-
iconType="arrowLeft"
187-
href={backButtonUrl!}
188-
onClick={handleBackOnClick}
189-
textProps={{ className: 'text' }}
190-
data-test-subj="backToOrigin"
191-
>
192-
{backButtonLabel || (
193-
<FormattedMessage
194-
id="xpack.securitySolution.trustedapps.list.backButton"
195-
defaultMessage="Back"
196-
/>
197-
)}
198-
</EuiButtonEmptyStyled>
199-
);
200-
}
201-
);
202-
203-
BackToExternalAppButton.displayName = 'BackToExternalAppButton';

0 commit comments

Comments
 (0)