Skip to content

Commit

Permalink
[Security Solution] Update open timeline filters and add unit tests (#…
Browse files Browse the repository at this point in the history
…89852) (#90773)

* update filter and add unit tests

* styling

* fix i18n error

* fix unit test

* fix lint error

Co-authored-by: Kibana Machine <[email protected]>

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
angorayc and kibanamachine authored Feb 9, 2021
1 parent c8b1485 commit 3162365
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { EuiModalBody, EuiModalHeader } from '@elastic/eui';
import { EuiModalBody, EuiModalHeader, EuiSpacer } from '@elastic/eui';
import React, { Fragment, memo, useMemo } from 'react';
import styled from 'styled-components';

Expand Down Expand Up @@ -62,11 +62,10 @@ export const OpenTimelineModalBody = memo<OpenTimelineProps>(
const SearchRowContent = useMemo(
() => (
<Fragment key="search-row-content">
{!!timelineFilter && timelineFilter}
{!!templateTimelineFilter && templateTimelineFilter}
</Fragment>
),
[timelineFilter, templateTimelineFilter]
[templateTimelineFilter]
);

return (
Expand All @@ -84,9 +83,14 @@ export const OpenTimelineModalBody = memo<OpenTimelineProps>(

<EuiModalBody>
<>
{!!timelineFilter && (
<>
{timelineFilter}
<EuiSpacer size="m" />
</>
)}
<SearchRow
data-test-subj="search-row"
favoriteCount={favoriteCount}
onlyFavorites={onlyFavorites}
onQueryChange={onQueryChange}
onToggleOnlyFavorites={onToggleOnlyFavorites}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Props = Pick<OpenTimelineProps, 'onAddTimelinesToFavorites' | 'title'> & {
*/
export const TitleRow = React.memo<Props>(
({ children, onAddTimelinesToFavorites, selectedTimelinesCount, title }) => (
<HeaderSection title={title} split={true}>
<HeaderSection title={title} split={true} height={40}>
<EuiFlexGroup gutterSize="s" responsive={false}>
{onAddTimelinesToFavorites && (
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const OPEN_TIMELINE = i18n.translate(
export const OPEN_TIMELINE_TITLE = i18n.translate(
'xpack.securitySolution.open.timeline.openTimelineTitle',
{
defaultMessage: 'Open Timeline',
defaultMessage: 'Open',
}
);

Expand Down Expand Up @@ -274,12 +274,6 @@ export const SUCCESSFULLY_EXPORTED_TIMELINE_TEMPLATES = (totalTimelineTemplates:
}
);

export const FILTER_TIMELINES = (timelineType: string) =>
i18n.translate('xpack.securitySolution.open.timeline.filterByTimelineTypesTitle', {
values: { timelineType },
defaultMessage: 'Only {timelineType}',
});

export const TAB_TIMELINES = i18n.translate(
'xpack.securitySolution.timelines.components.tabs.timelinesTitle',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,11 @@ export enum TimelineTabsStyle {
}

export interface TimelineTab {
count: number | undefined;
disabled: boolean;
href: string;
id: TimelineTypeLiteral;
name: string;
onClick: (ev: { preventDefault: () => void }) => void;
withNext: boolean;
}

export interface TemplateTimelineFilter {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* 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 from 'react';
import { fireEvent, render } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react-hooks';
import {
useTimelineTypes,
UseTimelineTypesArgs,
UseTimelineTypesResult,
} from './use_timeline_types';

jest.mock('react-router-dom', () => {
return {
useParams: jest.fn().mockReturnValue('default'),
useHistory: jest.fn().mockReturnValue([]),
};
});

jest.mock('../../../common/components/link_to', () => {
return {
getTimelineTabsUrl: jest.fn(),
useFormatUrl: jest.fn().mockReturnValue({
formatUrl: jest.fn(),
search: '',
}),
};
});

describe('useTimelineTypes', () => {
it('init', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();
expect(result.current).toEqual({
timelineType: 'default',
timelineTabs: result.current.timelineTabs,
timelineFilters: result.current.timelineFilters,
});
});
});

describe('timelineTabs', () => {
it('render timelineTabs', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();

const { container } = render(result.current.timelineTabs);
expect(
container.querySelector('[data-test-subj="timeline-tab-default"]')
).toHaveTextContent('Timelines');
expect(
container.querySelector('[data-test-subj="timeline-tab-template"]')
).toHaveTextContent('Templates');
});
});

it('set timelineTypes correctly', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();

const { container } = render(result.current.timelineTabs);

fireEvent(
container.querySelector('[data-test-subj="timeline-tab-template"]')!,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);

expect(result.current).toEqual({
timelineType: 'template',
timelineTabs: result.current.timelineTabs,
timelineFilters: result.current.timelineFilters,
});
});
});

it('stays in the same tab if clicking again on current tab', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();

const { container } = render(result.current.timelineTabs);

fireEvent(
container.querySelector('[data-test-subj="timeline-tab-default"]')!,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);

expect(result.current).toEqual({
timelineType: 'default',
timelineTabs: result.current.timelineTabs,
timelineFilters: result.current.timelineFilters,
});
});
});
});

describe('timelineFilters', () => {
it('render timelineFilters', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();

const { container } = render(<>{result.current.timelineFilters}</>);
expect(
container.querySelector('[data-test-subj="open-timeline-modal-body-filter-default"]')
).toHaveTextContent('Timelines');
expect(
container.querySelector('[data-test-subj="open-timeline-modal-body-filter-template"]')
).toHaveTextContent('Templates');
});
});

it('set timelineTypes correctly', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();

const { container } = render(<>{result.current.timelineFilters}</>);

fireEvent(
container.querySelector('[data-test-subj="open-timeline-modal-body-filter-template"]')!,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);

expect(result.current).toEqual({
timelineType: 'template',
timelineTabs: result.current.timelineTabs,
timelineFilters: result.current.timelineFilters,
});
});
});

it('stays in the same tab if clicking again on current tab', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<
UseTimelineTypesArgs,
UseTimelineTypesResult
>(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }));
await waitForNextUpdate();

const { container } = render(<>{result.current.timelineFilters}</>);

fireEvent(
container.querySelector('[data-test-subj="open-timeline-modal-body-filter-default"]')!,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);

expect(result.current).toEqual({
timelineType: 'default',
timelineTabs: result.current.timelineTabs,
timelineFilters: result.current.timelineFilters,
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React, { useState, useCallback, useMemo } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { EuiTabs, EuiTab, EuiSpacer, EuiFilterButton } from '@elastic/eui';
import { EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui';

import { noop } from 'lodash/fp';
import { TimelineTypeLiteralWithNull, TimelineType } from '../../../../common/types/timeline';
Expand All @@ -24,7 +24,7 @@ export interface UseTimelineTypesArgs {
export interface UseTimelineTypesResult {
timelineType: TimelineTypeLiteralWithNull;
timelineTabs: JSX.Element;
timelineFilters: JSX.Element[];
timelineFilters: JSX.Element;
}

export const useTimelineTypes = ({
Expand Down Expand Up @@ -59,51 +59,28 @@ export const useTimelineTypes = ({
(timelineTabsStyle: TimelineTabsStyle) => [
{
id: TimelineType.default,
name:
timelineTabsStyle === TimelineTabsStyle.filter
? i18n.FILTER_TIMELINES(i18n.TAB_TIMELINES)
: i18n.TAB_TIMELINES,
name: i18n.TAB_TIMELINES,
href: formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)),
disabled: false,
withNext: true,
count:
timelineTabsStyle === TimelineTabsStyle.filter
? defaultTimelineCount ?? undefined
: undefined,

onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTimeline : noop,
},
{
id: TimelineType.template,
name:
timelineTabsStyle === TimelineTabsStyle.filter
? i18n.FILTER_TIMELINES(i18n.TAB_TEMPLATES)
: i18n.TAB_TEMPLATES,
name: i18n.TAB_TEMPLATES,
href: formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)),
disabled: false,
withNext: false,
count:
timelineTabsStyle === TimelineTabsStyle.filter
? templateTimelineCount ?? undefined
: undefined,

onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTemplateTimeline : noop,
},
],
[
defaultTimelineCount,
templateTimelineCount,
urlSearch,
formatUrl,
goToTimeline,
goToTemplateTimeline,
]
[urlSearch, formatUrl, goToTimeline, goToTemplateTimeline]
);

const onFilterClicked = useCallback(
(tabId, tabStyle: TimelineTabsStyle) => {
setTimelineTypes((prevTimelineTypes) => {
if (tabId === prevTimelineTypes && tabStyle === TimelineTabsStyle.filter) {
return tabId === TimelineType.default ? TimelineType.template : TimelineType.default;
} else if (prevTimelineTypes !== tabId) {
if (prevTimelineTypes !== tabId) {
setTimelineTypes(tabId);
}
return prevTimelineTypes;
Expand Down Expand Up @@ -139,21 +116,23 @@ export const useTimelineTypes = ({
}, [tabName]);

const timelineFilters = useMemo(() => {
return getFilterOrTabs(TimelineTabsStyle.filter).map((tab: TimelineTab) => (
<EuiFilterButton
data-test-subj={`open-timeline-modal-body-${TimelineTabsStyle.filter}-${tab.id}`}
hasActiveFilters={tab.id === timelineType}
key={`timeline-${TimelineTabsStyle.filter}-${tab.id}`}
numFilters={tab.count}
onClick={(ev: { preventDefault: () => void }) => {
tab.onClick(ev);
onFilterClicked(tab.id, TimelineTabsStyle.filter);
}}
withNext={tab.withNext}
>
{tab.name}
</EuiFilterButton>
));
return (
<EuiTabs>
{getFilterOrTabs(TimelineTabsStyle.filter).map((tab: TimelineTab) => (
<EuiTab
data-test-subj={`open-timeline-modal-body-${TimelineTabsStyle.filter}-${tab.id}`}
isSelected={tab.id === timelineType}
key={`timeline-${TimelineTabsStyle.filter}-${tab.id}`}
onClick={(ev: { preventDefault: () => void }) => {
tab.onClick(ev);
onFilterClicked(tab.id, TimelineTabsStyle.filter);
}}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
);
}, [timelineType, getFilterOrTabs, onFilterClicked]);

return {
Expand Down
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 @@ -19258,7 +19258,6 @@
"xpack.securitySolution.open.timeline.exportSelectedButton": "選択した項目のエクスポート",
"xpack.securitySolution.open.timeline.favoriteSelectedButton": "選択中のお気に入り",
"xpack.securitySolution.open.timeline.favoritesTooltip": "お気に入り",
"xpack.securitySolution.open.timeline.filterByTimelineTypesTitle": "{timelineType}のみ",
"xpack.securitySolution.open.timeline.lastModifiedTableHeader": "最終更新:",
"xpack.securitySolution.open.timeline.missingSavedObjectIdTooltip": "savedObjectId がありません",
"xpack.securitySolution.open.timeline.modifiedByTableHeader": "変更者:",
Expand Down
Loading

0 comments on commit 3162365

Please sign in to comment.