Skip to content

Commit

Permalink
[Security solution][Endpoint] Add back button when to the event filte…
Browse files Browse the repository at this point in the history
…rs 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 <[email protected]>
  • Loading branch information
dasansol92 and kibanamachine authored Jun 9, 2021
1 parent 16e66b8 commit e94a34d
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 67 deletions.
12 changes: 12 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1114,3 +1114,15 @@ export interface GetExceptionSummaryResponse {
macos: number;
linux: number;
}

/**
* Supported React-Router state for the Generic List page
*/
export interface ListPageRouteState {
/** Where the user should be redirected to when the `Back` button is clicked */
onBackButtonNavigateTo: Parameters<ApplicationStart['navigateToApp']>;
/** The URL for the `Back` button */
backButtonUrl?: string;
/** The label for the button */
backButtonLabel?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

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

import {
DeleteTrustedAppsRequestSchema,
Expand Down Expand Up @@ -133,15 +132,3 @@ export type TrustedApp = NewTrustedApp & {
updated_at: string;
updated_by: string;
};

/**
* Supported React-Router state for the Trusted Apps List page
*/
export interface TrustedAppsListPageRouteState {
/** Where the user should be redirected to when the `Back` button is clicked */
onBackButtonNavigateTo: Parameters<ApplicationStart['navigateToApp']>;
/** The URL for the `Back` button */
backButtonUrl?: string;
/** The label for the button */
backButtonLabel?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { EuiButtonEmpty } from '@elastic/eui';
import styled from 'styled-components';

import { ListPageRouteState } from '../../../../common/endpoint/types';

import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler';

const EuiButtonEmptyStyled = styled(EuiButtonEmpty)`
margin-bottom: ${({ theme }) => theme.eui.euiSizeS};
.euiIcon {
width: ${({ theme }) => theme.eui.euiIconSizes.small};
height: ${({ theme }) => theme.eui.euiIconSizes.small};
}
.text {
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
}
`;

export const BackToExternalAppButton = memo<ListPageRouteState>(
({ backButtonLabel, backButtonUrl, onBackButtonNavigateTo }) => {
const handleBackOnClick = useNavigateToAppEventHandler(...onBackButtonNavigateTo!);

return (
<EuiButtonEmptyStyled
flush="left"
size="xs"
iconType="arrowLeft"
href={backButtonUrl!}
onClick={handleBackOnClick}
textProps={{ className: 'text' }}
data-test-subj="backToOrigin"
>
{backButtonLabel || (
<FormattedMessage id="xpack.securitySolution.list.backButton" defaultMessage="Back" />
)}
</EuiButtonEmptyStyled>
);
}
);

BackToExternalAppButton.displayName = 'BackToExternalAppButton';
Original file line number Diff line number Diff line change
@@ -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 { BackToExternalAppButton } from './back_to_external_app_button';
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,30 @@ describe('When on the Event Filters List Page', () => {
});
});
});

describe('and the back button is present', () => {
beforeEach(async () => {
renderResult = render();
act(() => {
history.push('/event_filters', {
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 not present', () => {
act(() => {
history.push('/event_filters');
});
expect(renderResult.queryByTestId('backToOrigin')).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* 2.0.
*/

import React, { memo, useCallback, useEffect } from 'react';
import React, { memo, useCallback, useMemo, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { useHistory } from 'react-router-dom';
import { useHistory, useLocation } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButton, EuiSpacer, EuiHorizontalRule, EuiText } from '@elastic/eui';
Expand All @@ -34,14 +34,15 @@ import {
showDeleteModal,
} from '../store/selector';
import { PaginatedContent, PaginatedContentProps } from '../../../components/paginated_content';
import { Immutable } from '../../../../../common/endpoint/types';
import { Immutable, ListPageRouteState } from '../../../../../common/endpoint/types';
import {
ExceptionItem,
ExceptionItemProps,
} from '../../../../common/components/exceptions/viewer/exception_item';
import { EventFilterDeleteModal } from './components/event_filter_delete_modal';

import { SearchBar } from '../../../components/search_bar';
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';

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

export const EventFiltersListPage = memo(() => {
const { state: routeState } = useLocation<ListPageRouteState | undefined>();
const history = useHistory();
const dispatch = useDispatch<Dispatch<AppAction>>();
const isActionError = useEventFiltersSelector(getActionError);
Expand Down Expand Up @@ -103,6 +105,13 @@ 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({
Expand Down Expand Up @@ -173,6 +182,7 @@ export const EventFiltersListPage = memo(() => {
return (
<AdministrationListPage
beta={false}
headerBackComponent={backButton}
title={
<FormattedMessage
id="xpack.securitySolution.eventFilters.list.pageTitle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
} from '../../../../../../../../../fleet/public';
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
import { getEventFiltersListPath } from '../../../../../../common/routing';
import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
import {
GetExceptionSummaryResponse,
ListPageRouteState,
} from '../../../../../../../../common/endpoint/types';
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
import { useToasts } from '../../../../../../../common/lib/kibana';
Expand Down Expand Up @@ -64,7 +67,7 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(
};
}, [eventFiltersApi, toasts]);

const eventFiltersRouteState = useMemo(() => {
const eventFiltersRouteState = useMemo<ListPageRouteState>(() => {
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
return {
backButtonLabel: i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
import { getTrustedAppsListPath } from '../../../../../../common/routing';
import {
TrustedAppsListPageRouteState,
ListPageRouteState,
GetExceptionSummaryResponse,
} from '../../../../../../../../common/endpoint/types';
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
Expand Down Expand Up @@ -67,7 +67,7 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>((
}, [toasts, trustedAppsApi]);
const trustedAppsListUrlPath = getTrustedAppsListPath();

const trustedAppRouteState = useMemo<TrustedAppsListPageRouteState>(() => {
const trustedAppRouteState = useMemo<ListPageRouteState>(() => {
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
return {
backButtonLabel: i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,4 +900,34 @@ describe('When on the Trusted Apps Page', () => {
});
});
});

describe('and the back button is present', () => {
let renderResult: ReturnType<AppContextTestRender['render']>;
beforeEach(async () => {
renderResult = render();
await act(async () => {
await waitForAction('trustedAppsListResourceStateChanged');
});
reactTestingLibrary.act(() => {
history.push('/trusted_apps', {
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 not present', () => {
reactTestingLibrary.act(() => {
history.push('/trusted_apps');
});
expect(renderResult.queryByTestId('backToOrigin')).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@

import React, { memo, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
EuiButtonEmpty,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
Expand All @@ -35,14 +33,14 @@ import { TrustedAppsGrid } from './components/trusted_apps_grid';
import { TrustedAppsList } from './components/trusted_apps_list';
import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog';
import { TrustedAppsNotifications } from './trusted_apps_notifications';
import { TrustedAppsListPageRouteState } from '../../../../../common/endpoint/types';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { ABOUT_TRUSTED_APPS, SEARCH_TRUSTED_APP_PLACEHOLDER } from './translations';
import { EmptyState } from './components/empty_state';
import { SearchBar } from '../../../components/search_bar';
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
import { ListPageRouteState } from '../../../../../common/endpoint/types';

export const TrustedAppsPage = memo(() => {
const { state: routeState } = useLocation<TrustedAppsListPageRouteState | undefined>();
const { state: routeState } = useLocation<ListPageRouteState | undefined>();
const location = useTrustedAppsSelector(getCurrentLocation);
const totalItemsCount = useTrustedAppsSelector(getListTotalItemsCount);
const isCheckingIfEntriesExists = useTrustedAppsSelector(checkingIfEntriesExist);
Expand Down Expand Up @@ -161,43 +159,3 @@ export const TrustedAppsPage = memo(() => {
});

TrustedAppsPage.displayName = 'TrustedAppsPage';

const EuiButtonEmptyStyled = styled(EuiButtonEmpty)`
margin-bottom: ${({ theme }) => theme.eui.euiSizeS};
.euiIcon {
width: ${({ theme }) => theme.eui.euiIconSizes.small};
height: ${({ theme }) => theme.eui.euiIconSizes.small};
}
.text {
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
}
`;

const BackToExternalAppButton = memo<TrustedAppsListPageRouteState>(
({ backButtonLabel, backButtonUrl, onBackButtonNavigateTo }) => {
const handleBackOnClick = useNavigateToAppEventHandler(...onBackButtonNavigateTo!);

return (
<EuiButtonEmptyStyled
flush="left"
size="xs"
iconType="arrowLeft"
href={backButtonUrl!}
onClick={handleBackOnClick}
textProps={{ className: 'text' }}
data-test-subj="backToOrigin"
>
{backButtonLabel || (
<FormattedMessage
id="xpack.securitySolution.trustedapps.list.backButton"
defaultMessage="Back"
/>
)}
</EuiButtonEmptyStyled>
);
}
);

BackToExternalAppButton.displayName = 'BackToExternalAppButton';
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -21393,7 +21393,6 @@
"xpack.securitySolution.trustedapps.list.actions.delete": "削除",
"xpack.securitySolution.trustedapps.list.actions.delete.description": "このエントリを削除",
"xpack.securitySolution.trustedapps.list.addButton": "信頼できるアプリケーションを追加",
"xpack.securitySolution.trustedapps.list.backButton": "戻る",
"xpack.securitySolution.trustedapps.list.columns.actions": "アクション",
"xpack.securitySolution.trustedapps.list.pageTitle": "信頼できるアプリケーション",
"xpack.securitySolution.trustedapps.listEmptyState.message": "現在、エンドポイントには信頼できるアプリケーションがありません。",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -21728,7 +21728,6 @@
"xpack.securitySolution.trustedapps.list.actions.delete": "移除",
"xpack.securitySolution.trustedapps.list.actions.delete.description": "移除此条目",
"xpack.securitySolution.trustedapps.list.addButton": "添加受信任的应用程序",
"xpack.securitySolution.trustedapps.list.backButton": "返回",
"xpack.securitySolution.trustedapps.list.columns.actions": "操作",
"xpack.securitySolution.trustedapps.list.pageTitle": "受信任的应用程序",
"xpack.securitySolution.trustedapps.list.totalCount": "{totalItemCount, plural, other {# 个受信任的应用程序}}",
Expand Down

0 comments on commit e94a34d

Please sign in to comment.