Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] template timeline UI #64439

Merged
merged 31 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
21c18f0
init template timeline's tab
angorayc Apr 14, 2020
898ebd5
Merge remote-tracking branch 'upstream/master' into template-timeline-ui
angorayc Apr 23, 2020
63846b8
Merge remote-tracking branch 'upstream/master' into template-timeline-ui
angorayc Apr 23, 2020
9612b5c
add template filter
angorayc Apr 24, 2020
dfef1e6
add routes for timelines tabs
angorayc Apr 27, 2020
159244d
add tabs hook
angorayc Apr 28, 2020
2adf4e0
Merge remote-tracking branch 'upstream/master' into template-timeline-ui
angorayc Apr 28, 2020
bc763e2
Merge remote-tracking branch 'upstream/master' into template-timeline-ui
angorayc Apr 29, 2020
c7b9c84
add filter type
angorayc Apr 29, 2020
2365287
fix unit test
angorayc Apr 30, 2020
6a1495e
add breadcrumbs
angorayc Apr 30, 2020
6afc706
fix types error
angorayc Apr 30, 2020
2865708
fix flashing table
angorayc May 3, 2020
0f6b679
fix types
angorayc May 3, 2020
88dbd74
fix flashing table
angorayc May 3, 2020
73a4b59
Merge branch 'master' into template-timeline-ui
elasticmachine May 4, 2020
fd4ad0d
Merge branch 'master' into template-timeline-ui
elasticmachine May 5, 2020
187df00
fix filter
patrykkopycinski May 5, 2020
d8336be
add comments for filters
angorayc May 5, 2020
99250b2
Merge remote-tracking branch 'upstream/master' into template-timeline-ui
angorayc May 5, 2020
5bd15e8
Merge remote-tracking branch 'origin/template-timeline-ui' into templ…
angorayc May 5, 2020
7a6f237
review X
XavierM May 5, 2020
467bdde
review x
XavierM May 5, 2020
faed495
fix create note for template timeline
angorayc May 5, 2020
1b506c7
rename timelineTypes to timelineType
angorayc May 5, 2020
1dd31e8
Merge branch 'template-timeline-ui' of github.com:angorayc/kibana int…
angorayc May 5, 2020
daf6441
update unit test
angorayc May 5, 2020
0090d22
fix types
angorayc May 5, 2020
929ad24
update filter for timeline savedObject
angorayc May 5, 2020
c42f1d7
fix lint error
angorayc May 5, 2020
8ef2860
Merge branch 'master' into template-timeline-ui
elasticmachine May 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion x-pack/plugins/siem/public/components/link_to/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export {
export { getOverviewUrl, RedirectToOverviewPage } from './redirect_to_overview';
export { getHostDetailsUrl, getHostsUrl } from './redirect_to_hosts';
export { getNetworkUrl, getIPDetailsUrl, RedirectToNetworkPage } from './redirect_to_network';
export { getTimelinesUrl, RedirectToTimelinesPage } from './redirect_to_timelines';
export {
getTimelinesUrl,
getTimelineTabsUrl,
RedirectToTimelinesPage,
} from './redirect_to_timelines';
export {
getCaseDetailsUrl,
getCaseUrl,
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/siem/public/components/link_to/link_to.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
RedirectToConfigureCasesPage,
} from './redirect_to_case';
import { DetectionEngineTab } from '../../pages/detection_engine/types';
import { TimelineType } from '../../../common/types/timeline';

interface LinkToPageProps {
match: RouteMatch<{}>;
Expand Down Expand Up @@ -112,8 +113,18 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
/>
<Route
component={RedirectToTimelinesPage}
exact
path={`${match.url}/:pageName(${SiemPageName.timelines})`}
/>
<Route
angorayc marked this conversation as resolved.
Show resolved Hide resolved
component={RedirectToTimelinesPage}
exact
path={`${match.url}/:pageName(${SiemPageName.timelines})`}
/>
<Route
component={RedirectToTimelinesPage}
path={`${match.url}/:pageName(${SiemPageName.timelines})/:tabName(${TimelineType.default}|${TimelineType.template})`}
/>
<Redirect to="/" />
</Switch>
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,30 @@ import { SiemPageName } from '../../pages/home/types';

import { appendSearch } from './helpers';
import { RedirectWrapper } from './redirect_wrapper';
import { TimelineTypeLiteral, TimelineType } from '../../../common/types/timeline';

export type TimelineComponentProps = RouteComponentProps<{
tabName: TimelineTypeLiteral;
search: string;
}>;

export const RedirectToTimelinesPage = ({ location: { search } }: TimelineComponentProps) => (
<RedirectWrapper to={`/${SiemPageName.timelines}${search}`} />
export const RedirectToTimelinesPage = ({
match: {
params: { tabName },
},
location: { search },
}: TimelineComponentProps) => (
<RedirectWrapper
to={
tabName
? `/${SiemPageName.timelines}/${tabName}${search}`
: `/${SiemPageName.timelines}/${TimelineType.default}${search}`
}
/>
);

export const getTimelinesUrl = (search?: string) =>
`#/link-to/${SiemPageName.timelines}${appendSearch(search)}`;

export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral, search?: string) =>
`#/link-to/${SiemPageName.timelines}/${tabName}${appendSearch(search)}`;
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ describe('Navigation Breadcrumbs', () => {
);
expect(breadcrumbs).toEqual([
{ text: 'SIEM', href: '#/link-to/overview' },
{ text: 'Timelines', href: '' },
{ text: 'Timelines', href: '#/link-to/timelines' },
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/host
import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details';
import { getBreadcrumbs as getCaseDetailsBreadcrumbs } from '../../../pages/case/utils';
import { getBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../pages/detection_engine/rules/utils';
import { getBreadcrumbs as getTimelinesBreadcrumbs } from '../../../pages/timelines';
import { SiemPageName } from '../../../pages/home/types';
import { RouteSpyState, HostRouteSpyState, NetworkRouteSpyState } from '../../../utils/route/types';
import {
RouteSpyState,
HostRouteSpyState,
NetworkRouteSpyState,
TimelineRouteSpyState,
} from '../../../utils/route/types';
import { getOverviewUrl } from '../../link_to';

import { TabNavigationProps } from '../tab_navigation/types';
Expand Down Expand Up @@ -44,6 +50,9 @@ const isNetworkRoutes = (spyState: RouteSpyState): spyState is NetworkRouteSpySt
const isHostsRoutes = (spyState: RouteSpyState): spyState is HostRouteSpyState =>
spyState != null && spyState.pageName === SiemPageName.hosts;

const isTimelinesRoutes = (spyState: RouteSpyState): spyState is TimelineRouteSpyState =>
spyState != null && spyState.pageName === SiemPageName.timelines;

const isCaseRoutes = (spyState: RouteSpyState): spyState is RouteSpyState =>
spyState != null && spyState.pageName === SiemPageName.case;

Expand Down Expand Up @@ -124,6 +133,24 @@ export const getBreadcrumbsForRoute = (
),
];
}
if (isTimelinesRoutes(spyState) && object.navTabs) {
const tempNav: SearchNavTab = { urlKey: 'timeline', isDetailPage: false };
let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)];
if (spyState.tabName != null) {
urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)];
}

return [
...siemRootBreadcrumb,
...getTimelinesBreadcrumbs(
spyState,
urlStateKeys.reduce(
(acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
[]
)
),
];
}
if (
spyState != null &&
object.navTabs &&
Expand Down
37 changes: 10 additions & 27 deletions x-pack/plugins/siem/public/components/open_timeline/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@ jest.mock('../../lib/kibana');
jest.mock('../../containers/timeline/all', () => {
const originalModule = jest.requireActual('../../containers/timeline/all');
return {
...originalModule,
useGetAllTimeline: jest.fn(),
getAllTimeline: originalModule.getAllTimeline,
};
});
jest.mock('./use_timeline_types', () => {
return {
useTimelineTypes: jest.fn().mockReturnValue({
timelineTypes: 'default',
timelineTabs: <div />,
timelineFilters: <div />,
}),
};
});

describe('StatefulOpenTimeline', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
Expand Down Expand Up @@ -491,33 +501,6 @@ describe('StatefulOpenTimeline', () => {
});
});

test('it renders the title', async () => {
angorayc marked this conversation as resolved.
Show resolved Hide resolved
const wrapper = mount(
<ThemeProvider theme={theme}>
<TestProviderWithoutDragAndDrop>
<MockedProvider mocks={mockOpenTimelineQueryResults} addTypename={false}>
<StatefulOpenTimeline
data-test-subj="stateful-timeline"
apolloClient={apolloClient}
isModal={false}
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
title={title}
/>
</MockedProvider>
</TestProviderWithoutDragAndDrop>
</ThemeProvider>
);

await wait();

expect(
wrapper
.find('[data-test-subj="header-section-title"]')
.first()
.text()
).toEqual(title);
});

describe('#resetSelectionState', () => {
test('when the user deletes selected timelines, resetSelectionState is invoked to clear the selection state', async () => {
const wrapper = mount(
Expand Down
26 changes: 12 additions & 14 deletions x-pack/plugins/siem/public/components/open_timeline/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { useEffect, useState, useCallback } from 'react';
import { connect, ConnectedProps } from 'react-redux';

import { Dispatch } from 'redux';

import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers';
import { deleteTimelineMutation } from '../../containers/timeline/delete/persist.gql_query';
import { useGetAllTimeline } from '../../containers/timeline/all';
Expand Down Expand Up @@ -40,6 +41,7 @@ import {
OnDeleteOneTimeline,
} from './types';
import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants';
import { useTimelineTypes } from './use_timeline_types';

interface OwnProps<TCache = object> {
apolloClient: ApolloClient<TCache>;
Expand Down Expand Up @@ -103,6 +105,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
/** The requested field to sort on */
const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD);

const { timelineTypes, timelineTabs, timelineFilters } = useTimelineTypes();
const { fetchAllTimeline, timelines, loading, totalCount, refetch } = useGetAllTimeline();

/** Invoked when the user presses enters to submit the text in the search input */
Expand Down Expand Up @@ -136,9 +139,12 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(

const deleteTimelines: DeleteTimelines = useCallback(
async (timelineIds: string[]) => {
const existingTimelines = [...timelines];
const existingTimelinesCount = totalCount;
if (timelineIds.includes(timeline.savedObjectId || '')) {
createNewTimeline({ id: 'timeline-1', columns: defaultHeaders, show: false });
}

await apolloClient.mutate<
DeleteTimelineMutation.Mutation,
DeleteTimelineMutation.Variables
Expand All @@ -147,9 +153,9 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
fetchPolicy: 'no-cache',
variables: { id: timelineIds },
});
refetch();
refetch(existingTimelines, existingTimelinesCount);
},
[apolloClient, createNewTimeline, refetch, timeline]
[apolloClient, createNewTimeline, refetch, timeline, timelines, totalCount]
);

const onDeleteOneTimeline: OnDeleteOneTimeline = useCallback(
Expand Down Expand Up @@ -237,19 +243,9 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
search,
sort: { sortField: sortField as SortFieldTimeline, sortOrder: sortDirection as Direction },
onlyUserFavorite: onlyFavorites,
timelines,
totalCount,
timelineTypes,
});
}, [
pageIndex,
pageSize,
search,
sortField,
sortDirection,
timelines,
totalCount,
onlyFavorites,
]);
}, [pageIndex, pageSize, search, sortField, sortDirection, timelineTypes, onlyFavorites]);

return !isModal ? (
<OpenTimeline
Expand Down Expand Up @@ -277,6 +273,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
selectedItems={selectedItems}
sortDirection={sortDirection}
sortField={sortField}
tabs={timelineTabs}
title={title}
totalSearchResultsCount={totalCount}
/>
Expand All @@ -303,6 +300,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
selectedItems={selectedItems}
sortDirection={sortDirection}
sortField={sortField}
tabs={timelineFilters}
title={title}
totalSearchResultsCount={totalCount}
/>
Expand Down
Loading