diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_case.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_case.tsx index 20ba0b50f5126..6ec15b55ba83d 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_case.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_case.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; +import { appendSearch } from './helpers'; import { RedirectWrapper } from './redirect_wrapper'; import { SiemPageName } from '../../pages/home/types'; @@ -30,8 +31,14 @@ export const RedirectToConfigureCasesPage = () => ( const baseCaseUrl = `#/link-to/${SiemPageName.case}`; -export const getCaseUrl = () => baseCaseUrl; -export const getCaseDetailsUrl = (detailName: string, search: string) => - `${baseCaseUrl}/${detailName}${search}`; -export const getCreateCaseUrl = (search: string) => `${baseCaseUrl}/create${search}`; -export const getConfigureCasesUrl = (search: string) => `${baseCaseUrl}/configure${search}`; +export const getCaseUrl = (search: string | null) => + `${baseCaseUrl}${appendSearch(search ?? undefined)}`; + +export const getCaseDetailsUrl = ({ id, search }: { id: string; search: string | null }) => + `${baseCaseUrl}/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`; + +export const getCreateCaseUrl = (search: string | null) => + `${baseCaseUrl}/create${appendSearch(search ?? undefined)}`; + +export const getConfigureCasesUrl = (search: string) => + `${baseCaseUrl}/configure${appendSearch(search ?? undefined)}`; diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.tsx index 935df9ad3361f..14dc5e7999a65 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.tsx @@ -23,11 +23,11 @@ import { import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types'; import { useUiSetting$ } from '../../lib/kibana'; import { IP_REPUTATION_LINKS_SETTING } from '../../../common/constants'; -import { navTabs } from '../../pages/home/home_navigations'; import * as i18n from '../page/network/ip_overview/translations'; import { isUrlInvalid } from '../../pages/detection_engine/rules/components/step_about_rule/helpers'; -import { useGetUrlSearch } from '../navigation/use_get_url_search'; import { ExternalLinkIcon } from '../external_link_icon'; +import { navTabs } from '../../pages/home/home_navigations'; +import { useGetUrlSearch } from '../navigation/use_get_url_search'; export const DEFAULT_NUMBER_OF_LINK = 5; @@ -92,10 +92,11 @@ const CaseDetailsLinkComponent: React.FC<{ children?: React.ReactNode; detailNam children, detailName, }) => { - const urlSearch = useGetUrlSearch(navTabs.case); + const search = useGetUrlSearch(navTabs.case); + return ( {children ? children : detailName} @@ -106,8 +107,8 @@ export const CaseDetailsLink = React.memo(CaseDetailsLinkComponent); CaseDetailsLink.displayName = 'CaseDetailsLink'; export const CreateCaseLink = React.memo<{ children: React.ReactNode }>(({ children }) => { - const urlSearch = useGetUrlSearch(navTabs.case); - return {children}; + const search = useGetUrlSearch(navTabs.case); + return {children}; }); CreateCaseLink.displayName = 'CreateCaseLink'; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx index de662c162fc0a..89af9202a597e 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx @@ -126,6 +126,17 @@ describe('Markdown', () => { ).toHaveProperty('href', 'https://google.com/'); }); + test('it does NOT render the href if links are disabled', () => { + const wrapper = mount(); + + expect( + wrapper + .find('[data-test-subj="markdown-link"]') + .first() + .getDOMNode() + ).not.toHaveProperty('href'); + }); + test('it opens links in a new tab via target="_blank"', () => { const wrapper = mount(); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx index 30695c9d0c7e2..1368c13619d6b 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx @@ -26,51 +26,53 @@ const REL_NOFOLLOW = 'nofollow'; /** prevents the browser from sending the current address as referrer via the Referer HTTP header */ const REL_NOREFERRER = 'noreferrer'; -export const Markdown = React.memo<{ raw?: string; size?: 'xs' | 's' | 'm' }>( - ({ raw, size = 's' }) => { - const markdownRenderers = { - root: ({ children }: { children: React.ReactNode[] }) => ( - +export const Markdown = React.memo<{ + disableLinks?: boolean; + raw?: string; + size?: 'xs' | 's' | 'm'; +}>(({ disableLinks = false, raw, size = 's' }) => { + const markdownRenderers = { + root: ({ children }: { children: React.ReactNode[] }) => ( + + {children} + + ), + table: ({ children }: { children: React.ReactNode[] }) => ( + + {children} +
+ ), + tableHead: ({ children }: { children: React.ReactNode[] }) => ( + {children} + ), + tableRow: ({ children }: { children: React.ReactNode[] }) => ( + {children} + ), + tableCell: ({ children }: { children: React.ReactNode[] }) => ( + {children} + ), + link: ({ children, href }: { children: React.ReactNode[]; href?: string }) => ( + + {children} -
- ), - table: ({ children }: { children: React.ReactNode[] }) => ( - - {children} -
- ), - tableHead: ({ children }: { children: React.ReactNode[] }) => ( - {children} - ), - tableRow: ({ children }: { children: React.ReactNode[] }) => ( - {children} - ), - tableCell: ({ children }: { children: React.ReactNode[] }) => ( - {children} - ), - link: ({ children, href }: { children: React.ReactNode[]; href?: string }) => ( - - - {children} - - - ), - }; +
+ + ), + }; - return ( - - ); - } -); + return ( + + ); +}); Markdown.displayName = 'Markdown'; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts index e25fb4374bb14..155f63145ca95 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts @@ -107,7 +107,22 @@ export const getBreadcrumbsForRoute = ( ]; } if (isCaseRoutes(spyState) && object.navTabs) { - return [...siemRootBreadcrumb, ...getCaseDetailsBreadcrumbs(spyState)]; + const tempNav: SearchNavTab = { urlKey: 'case', isDetailPage: false }; + let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)]; + if (spyState.tabName != null) { + urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)]; + } + + return [ + ...siemRootBreadcrumb, + ...getCaseDetailsBreadcrumbs( + spyState, + urlStateKeys.reduce( + (acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)], + [] + ) + ), + ]; } if ( spyState != null && diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx index 98eea1eaa6454..cd356212b4400 100644 --- a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer } from '@elastic/eui'; import React from 'react'; import { LoadingPlaceholders } from '../page/overview/loading_placeholders'; @@ -30,12 +29,7 @@ const NewsFeedComponent: React.FC = ({ news }) => ( ) : news.length === 0 ? ( ) : ( - news.map((n: NewsItem) => ( - - - - - )) + news.map((n: NewsItem) => ) )} ); diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx index cb2542a497f08..9cab78c9f20b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx @@ -45,6 +45,7 @@ export const Post = React.memo<{ newsItem: NewsItem }>(({ newsItem }) => {
{description}
+ diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/filters/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/filters/index.tsx new file mode 100644 index 0000000000000..edb0b99cbff8b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/filters/index.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButtonGroup, EuiButtonGroupOption } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; + +import { FilterMode } from '../types'; + +import * as i18n from '../translations'; + +const MY_RECENTLY_REPORTED_ID = 'myRecentlyReported'; + +const toggleButtonIcons: EuiButtonGroupOption[] = [ + { + id: 'recentlyCreated', + label: i18n.RECENTLY_CREATED_CASES, + iconType: 'folderExclamation', + }, + { + id: MY_RECENTLY_REPORTED_ID, + label: i18n.MY_RECENTLY_REPORTED_CASES, + iconType: 'reporter', + }, +]; + +export const Filters = React.memo<{ + filterBy: FilterMode; + setFilterBy: (filterBy: FilterMode) => void; + showMyRecentlyReported: boolean; +}>(({ filterBy, setFilterBy, showMyRecentlyReported }) => { + const options = useMemo( + () => + showMyRecentlyReported + ? toggleButtonIcons + : toggleButtonIcons.filter(x => x.id !== MY_RECENTLY_REPORTED_ID), + [showMyRecentlyReported] + ); + const onChange = useCallback( + (filterMode: string) => { + setFilterBy(filterMode as FilterMode); + }, + [setFilterBy] + ); + + return ; +}); + +Filters.displayName = 'Filters'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/index.tsx new file mode 100644 index 0000000000000..07246c6c6ec88 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/index.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; +import React, { useEffect, useMemo, useRef } from 'react'; + +import { FilterOptions, QueryParams } from '../../containers/case/types'; +import { DEFAULT_QUERY_PARAMS, useGetCases } from '../../containers/case/use_get_cases'; +import { getCaseUrl } from '../link_to/redirect_to_case'; +import { useGetUrlSearch } from '../navigation/use_get_url_search'; +import { LoadingPlaceholders } from '../page/overview/loading_placeholders'; +import { navTabs } from '../../pages/home/home_navigations'; + +import { NoCases } from './no_cases'; +import { RecentCases } from './recent_cases'; +import * as i18n from './translations'; + +const usePrevious = (value: FilterOptions) => { + const ref = useRef(); + useEffect(() => { + (ref.current as unknown) = value; + }); + return ref.current; +}; + +const MAX_CASES_TO_SHOW = 3; + +const queryParams: QueryParams = { + ...DEFAULT_QUERY_PARAMS, + perPage: MAX_CASES_TO_SHOW, +}; + +const StatefulRecentCasesComponent = React.memo( + ({ filterOptions }: { filterOptions: FilterOptions }) => { + const previousFilterOptions = usePrevious(filterOptions); + const { data, loading, setFilters } = useGetCases(queryParams); + const isLoadingCases = useMemo( + () => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1, + [loading] + ); + const search = useGetUrlSearch(navTabs.case); + const allCasesLink = useMemo( + () => {i18n.VIEW_ALL_CASES}, + [search] + ); + + useEffect(() => { + if (previousFilterOptions !== undefined && previousFilterOptions !== filterOptions) { + setFilters(filterOptions); + } + }, [previousFilterOptions, filterOptions, setFilters]); + + const content = useMemo( + () => + isLoadingCases ? ( + + ) : !isLoadingCases && data.cases.length === 0 ? ( + + ) : ( + + ), + [isLoadingCases, data] + ); + + return ( + + {content} + + {allCasesLink} + + ); + } +); + +StatefulRecentCasesComponent.displayName = 'StatefulRecentCasesComponent'; + +export const StatefulRecentCases = React.memo(StatefulRecentCasesComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/no_cases/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/no_cases/index.tsx new file mode 100644 index 0000000000000..9f0361311b7b6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/no_cases/index.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLink } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { getCreateCaseUrl } from '../../link_to/redirect_to_case'; +import { useGetUrlSearch } from '../../navigation/use_get_url_search'; +import { navTabs } from '../../../pages/home/home_navigations'; + +import * as i18n from '../translations'; + +const NoCasesComponent = () => { + const urlSearch = useGetUrlSearch(navTabs.case); + const newCaseLink = useMemo( + () => {` ${i18n.START_A_NEW_CASE}`}, + [urlSearch] + ); + + return ( + <> + {i18n.NO_CASES} + {newCaseLink} + {'!'} + + ); +}; + +NoCasesComponent.displayName = 'NoCasesComponent'; + +export const NoCases = React.memo(NoCasesComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/recent_cases.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/recent_cases.tsx new file mode 100644 index 0000000000000..eb17c75f4111b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/recent_cases.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +import { Case } from '../../containers/case/types'; +import { getCaseDetailsUrl } from '../link_to/redirect_to_case'; +import { Markdown } from '../markdown'; +import { useGetUrlSearch } from '../navigation/use_get_url_search'; +import { navTabs } from '../../pages/home/home_navigations'; +import { IconWithCount } from '../recent_timelines/counts'; + +import * as i18n from './translations'; + +const MarkdownContainer = styled.div` + max-height: 150px; + overflow-y: auto; + width: 300px; +`; + +const RecentCasesComponent = ({ cases }: { cases: Case[] }) => { + const search = useGetUrlSearch(navTabs.case); + + return ( + <> + {cases.map((c, i) => ( + + + + {c.title} + + + + {c.description && c.description.length && ( + + + + + + )} + {i !== cases.length - 1 && } + + + ))} + + ); +}; + +RecentCasesComponent.displayName = 'RecentCasesComponent'; + +export const RecentCases = React.memo(RecentCasesComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/translations.ts b/x-pack/legacy/plugins/siem/public/components/recent_cases/translations.ts new file mode 100644 index 0000000000000..d2318e5db88c3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/translations.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const COMMENTS = i18n.translate('xpack.siem.recentCases.commentsTooltip', { + defaultMessage: 'Comments', +}); + +export const MY_RECENTLY_REPORTED_CASES = i18n.translate( + 'xpack.siem.overview.myRecentlyReportedCasesButtonLabel', + { + defaultMessage: 'My recently reported cases', + } +); + +export const NO_CASES = i18n.translate('xpack.siem.recentCases.noCasesMessage', { + defaultMessage: 'No cases have been created yet. Put your detective hat on and', +}); + +export const RECENTLY_CREATED_CASES = i18n.translate( + 'xpack.siem.overview.recentlyCreatedCasesButtonLabel', + { + defaultMessage: 'Recently created cases', + } +); + +export const START_A_NEW_CASE = i18n.translate('xpack.siem.recentCases.startNewCaseLink', { + defaultMessage: 'start a new case', +}); + +export const VIEW_ALL_CASES = i18n.translate('xpack.siem.recentCases.viewAllCasesLink', { + defaultMessage: 'View all cases', +}); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/types.ts b/x-pack/legacy/plugins/siem/public/components/recent_cases/types.ts new file mode 100644 index 0000000000000..29c7072ce0be6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/types.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type FilterMode = 'recentlyCreated' | 'myRecentlyReported'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx index e04b6319cfb24..c80530b245cf3 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx @@ -21,7 +21,7 @@ const FlexGroup = styled(EuiFlexGroup)` margin-right: 16px; `; -const IconWithCount = React.memo<{ count: number; icon: string; tooltip: string }>( +export const IconWithCount = React.memo<{ count: number; icon: string; tooltip: string }>( ({ count, icon, tooltip }) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx index de8a3de8094d0..d7271197b9cea 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx @@ -9,15 +9,17 @@ import React from 'react'; import { FilterMode } from '../types'; +import * as i18n from '../translations'; + const toggleButtonIcons: EuiButtonGroupOption[] = [ { id: 'favorites', - label: 'Favorites', + label: i18n.FAVORITES, iconType: 'starFilled', }, { id: `recently-updated`, - label: 'Last updated', + label: i18n.LAST_UPDATED, iconType: 'documentEdit', }, ]; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx index 007665b47dedb..5b851701b973c 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx @@ -31,6 +31,8 @@ interface OwnProps { export type Props = OwnProps & PropsFromRedux; +const PAGE_SIZE = 3; + const StatefulRecentTimelinesComponent = React.memo( ({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => { const onOpenTimeline: OnOpenTimeline = useCallback( @@ -53,12 +55,18 @@ const StatefulRecentTimelinesComponent = React.memo( () => {i18n.VIEW_ALL_TIMELINES}, [urlSearch] ); + const loadingPlaceholders = useMemo( + () => ( + + ), + [filterBy] + ); return ( ( {({ timelines, loading }) => ( <> {loading ? ( - + loadingPlaceholders ) : ( {t.description && t.description.length && ( - <> - - - {t.description} - - + + {t.description} + )} diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts b/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts index e547272fde6e1..f5934aa317242 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts @@ -13,6 +13,10 @@ export const ERROR_RETRIEVING_USER_DETAILS = i18n.translate( } ); +export const FAVORITES = i18n.translate('xpack.siem.recentTimelines.favoritesButtonLabel', { + defaultMessage: 'Favorites', +}); + export const NO_FAVORITE_TIMELINES = i18n.translate( 'xpack.siem.recentTimelines.noFavoriteTimelinesMessage', { @@ -21,6 +25,10 @@ export const NO_FAVORITE_TIMELINES = i18n.translate( } ); +export const LAST_UPDATED = i18n.translate('xpack.siem.recentTimelines.lastUpdatedButtonLabel', { + defaultMessage: 'Last updated', +}); + export const NO_TIMELINES = i18n.translate('xpack.siem.recentTimelines.noTimelinesMessage', { defaultMessage: "You haven't created any timelines yet. Get out there and start threat hunting!", }); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx index 6e957313d9b04..4d2a717153894 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx @@ -157,9 +157,7 @@ describe('UrlStateContainer', () => { ).toEqual({ hash: '', pathname: examplePath, - search: [CONSTANTS.timelinePage].includes(page) - ? `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))` - : `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`, + search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`, state: '', }); } diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index c6f49d8a0e49b..9d8a4a8e6a908 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -64,15 +64,15 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, - CONSTANTS.timeline, CONSTANTS.timerange, + CONSTANTS.timeline, ], case: [ CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, - CONSTANTS.timeline, CONSTANTS.timerange, + CONSTANTS.timeline, ], }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx index 6c4a6ac4fe58a..ae7b8f3c043fa 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx @@ -88,6 +88,20 @@ const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesS } }; +export const DEFAULT_FILTER_OPTIONS: FilterOptions = { + search: '', + reporters: [], + status: 'open', + tags: [], +}; + +export const DEFAULT_QUERY_PARAMS: QueryParams = { + page: DEFAULT_TABLE_ACTIVE_PAGE, + perPage: DEFAULT_TABLE_LIMIT, + sortField: SortFieldCase.createdAt, + sortOrder: 'desc', +}; + const initialData: AllCases = { cases: [], countClosedCases: null, @@ -109,23 +123,14 @@ interface UseGetCases extends UseGetCasesState { setQueryParams: (queryParams: QueryParams) => void; setSelectedCases: (mySelectedCases: Case[]) => void; } -export const useGetCases = (): UseGetCases => { + +export const useGetCases = (initialQueryParams?: QueryParams): UseGetCases => { const [state, dispatch] = useReducer(dataFetchReducer, { data: initialData, - filterOptions: { - search: '', - reporters: [], - status: 'open', - tags: [], - }, + filterOptions: DEFAULT_FILTER_OPTIONS, isError: false, loading: [], - queryParams: { - page: DEFAULT_TABLE_ACTIVE_PAGE, - perPage: DEFAULT_TABLE_LIMIT, - sortField: SortFieldCase.createdAt, - sortOrder: 'desc', - }, + queryParams: initialQueryParams ?? DEFAULT_QUERY_PARAMS, selectedCases: [], }); const [, dispatchToaster] = useStateToaster(); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index 87a2ea888831a..cbb9ddae22d04 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -26,6 +26,7 @@ import { useGetCases, UpdateCase } from '../../../../containers/case/use_get_cas import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status'; import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { EuiBasicTableOnChange } from '../../../detection_engine/rules/types'; +import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search'; import { Panel } from '../../../../components/panel'; import { UtilityBar, @@ -35,16 +36,15 @@ import { UtilityBarText, } from '../../../../components/utility_bar'; import { getConfigureCasesUrl, getCreateCaseUrl } from '../../../../components/link_to'; -import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case'; -import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search'; -import { navTabs } from '../../../home/home_navigations'; import { getBulkItems } from '../bulk_actions'; import { CaseHeaderPage } from '../case_header_page'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { OpenClosedStats } from '../open_closed_stats'; +import { navTabs } from '../../../home/home_navigations'; import { getActions } from './actions'; import { CasesTableFilters } from './table_filters'; +import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case'; const Div = styled.div` margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; @@ -77,6 +77,7 @@ const getSortField = (field: string): SortFieldCase => { }; export const AllCases = React.memo(() => { const urlSearch = useGetUrlSearch(navTabs.case); + const { countClosedCases, countOpenCases, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 742921cb9f69e..5c20b53f5fcb9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -25,11 +25,13 @@ import { useGetCase } from '../../../../containers/case/use_get_case'; import { UserActionTree } from '../user_action_tree'; import { UserList } from '../user_list'; import { useUpdateCase } from '../../../../containers/case/use_update_case'; +import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search'; import { WrapperPage } from '../../../../components/wrapper_page'; import { getTypedPayload } from '../../../../containers/case/utils'; import { WhitePageWrapper } from '../wrappers'; import { useBasePath } from '../../../../lib/kibana'; import { CaseStatus } from '../case_status'; +import { navTabs } from '../../../home/home_navigations'; import { SpyRoute } from '../../../../utils/route/spy_routes'; import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions'; import { usePushToService } from './push_to_service'; @@ -61,6 +63,8 @@ export interface CaseProps { export const CaseComponent = React.memo(({ caseId, initialData }) => { const basePath = window.location.origin + useBasePath(); const caseLink = `${basePath}/app/siem#/case/${caseId}`; + const search = useGetUrlSearch(navTabs.case); + const [initLoadingData, setInitLoadingData] = useState(true); const { caseUserActions, @@ -190,7 +194,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) => css` @@ -73,6 +72,7 @@ const actionTypes: ActionType[] = [ ]; const ConfigureCasesComponent: React.FC = () => { + const search = useGetUrlSearch(navTabs.case); const { http, triggers_actions_ui, notifications, application } = useKibana().services; const [connectorIsValid, setConnectorIsValid] = useState(true); @@ -235,7 +235,7 @@ const ConfigureCasesComponent: React.FC = () => { isDisabled={isLoadingAny} isLoading={persistLoading} aria-label="Cancel" - href={CASE_URL} + href={getCaseUrl(search)} > {i18n.CANCEL} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx b/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx index b546a88744439..b7e7ced308331 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { WrapperPage } from '../../components/wrapper_page'; import { CaseHeaderPage } from './components/case_header_page'; @@ -13,11 +13,8 @@ import { getCaseUrl } from '../../components/link_to'; import { WhitePageWrapper, SectionWrapper } from './components/wrappers'; import * as i18n from './translations'; import { ConfigureCases } from './components/configure_cases'; - -const backOptions = { - href: getCaseUrl(), - text: i18n.BACK_TO_ALL, -}; +import { useGetUrlSearch } from '../../components/navigation/use_get_url_search'; +import { navTabs } from '../home/home_navigations'; const wrapperPageStyle: Record = { paddingLeft: '0', @@ -25,18 +22,30 @@ const wrapperPageStyle: Record = { paddingBottom: '0', }; -const ConfigureCasesPageComponent: React.FC = () => ( - <> - - - - - - - - - - -); +const ConfigureCasesPageComponent: React.FC = () => { + const search = useGetUrlSearch(navTabs.case); + + const backOptions = useMemo( + () => ({ + href: getCaseUrl(search), + text: i18n.BACK_TO_ALL, + }), + [search] + ); + + return ( + <> + + + + + + + + + + + ); +}; export const ConfigureCasesPage = React.memo(ConfigureCasesPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx index 2c7525264f71b..bd1f6da0ca28b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { WrapperPage } from '../../components/wrapper_page'; import { Create } from './components/create'; @@ -12,20 +12,29 @@ import { SpyRoute } from '../../utils/route/spy_routes'; import { CaseHeaderPage } from './components/case_header_page'; import * as i18n from './translations'; import { getCaseUrl } from '../../components/link_to'; +import { useGetUrlSearch } from '../../components/navigation/use_get_url_search'; +import { navTabs } from '../home/home_navigations'; -const backOptions = { - href: getCaseUrl(), - text: i18n.BACK_TO_ALL, -}; +export const CreateCasePage = React.memo(() => { + const search = useGetUrlSearch(navTabs.case); -export const CreateCasePage = React.memo(() => ( - <> - - - - - - -)); + const backOptions = useMemo( + () => ({ + href: getCaseUrl(search), + text: i18n.BACK_TO_ALL, + }), + [search] + ); + + return ( + <> + + + + + + + ); +}); CreateCasePage.displayName = 'CreateCasePage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/utils.ts b/x-pack/legacy/plugins/siem/public/pages/case/utils.ts index 3f2964b8cdd6d..df9f0d08e728c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/utils.ts @@ -4,16 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEmpty } from 'lodash/fp'; import { Breadcrumb } from 'ui/chrome'; + import { getCaseDetailsUrl, getCaseUrl, getCreateCaseUrl } from '../../components/link_to'; import { RouteSpyState } from '../../utils/route/types'; import * as i18n from './translations'; -export const getBreadcrumbs = (params: RouteSpyState): Breadcrumb[] => { +export const getBreadcrumbs = (params: RouteSpyState, search: string[]): Breadcrumb[] => { + const queryParameters = !isEmpty(search[0]) ? search[0] : null; + let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getCaseUrl(), + href: getCaseUrl(queryParameters), }, ]; if (params.detailName === 'create') { @@ -21,7 +25,7 @@ export const getBreadcrumbs = (params: RouteSpyState): Breadcrumb[] => { ...breadcrumb, { text: i18n.CREATE_BC_TITLE, - href: getCreateCaseUrl(''), + href: getCreateCaseUrl(queryParameters), }, ]; } else if (params.detailName != null) { @@ -29,7 +33,7 @@ export const getBreadcrumbs = (params: RouteSpyState): Breadcrumb[] => { ...breadcrumb, { text: params.state?.caseTitle ?? '', - href: getCaseDetailsUrl(params.detailName, ''), + href: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }), }, ]; } diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx index a087dca38de00..543469e2fddb7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx @@ -54,7 +54,7 @@ export const navTabs: SiemNavTab = { [SiemPageName.case]: { id: SiemPageName.case, name: i18n.CASE, - href: getCaseUrl(), + href: getCaseUrl(null), disabled: false, urlKey: 'case', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx index ad2821edde411..3797eae2bb853 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx @@ -6,13 +6,27 @@ import React, { useState } from 'react'; -import { FilterMode } from '../../../components/recent_timelines/types'; +import { FilterMode as RecentTimelinesFilterMode } from '../../../components/recent_timelines/types'; +import { FilterMode as RecentCasesFilterMode } from '../../../components/recent_cases/types'; + import { Sidebar } from './sidebar'; export const StatefulSidebar = React.memo(() => { - const [filterBy, setFilterBy] = useState('favorites'); + const [recentTimelinesFilterBy, setRecentTimelinesFilterBy] = useState( + 'favorites' + ); + const [recentCasesFilterBy, setRecentCasesFilterBy] = useState( + 'recentlyCreated' + ); - return ; + return ( + + ); }); StatefulSidebar.displayName = 'StatefulSidebar'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx index d3b85afe62a2a..52e36b472a0ec 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx @@ -8,12 +8,17 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { Filters } from '../../../components/recent_timelines/filters'; +import { Filters as RecentCasesFilters } from '../../../components/recent_cases/filters'; +import { Filters as RecentTimelinesFilters } from '../../../components/recent_timelines/filters'; import { ENABLE_NEWS_FEED_SETTING, NEWS_FEED_URL_SETTING } from '../../../../common/constants'; +import { StatefulRecentCases } from '../../../components/recent_cases'; import { StatefulRecentTimelines } from '../../../components/recent_timelines'; import { StatefulNewsFeed } from '../../../components/news_feed'; -import { FilterMode } from '../../../components/recent_timelines/types'; +import { FilterMode as RecentTimelinesFilterMode } from '../../../components/recent_timelines/types'; +import { FilterMode as RecentCasesFilterMode } from '../../../components/recent_cases/types'; +import { DEFAULT_FILTER_OPTIONS } from '../../../containers/case/use_get_cases'; import { SidebarHeader } from '../../../components/sidebar_header'; +import { useCurrentUser } from '../../../lib/kibana'; import { useApolloClient } from '../../../utils/apollo_context'; import * as i18n from '../translations'; @@ -22,35 +27,93 @@ const SidebarFlexGroup = styled(EuiFlexGroup)` width: 305px; `; +const SidebarSpacerComponent = () => ( + + + +); + +SidebarSpacerComponent.displayName = 'SidebarSpacerComponent'; +const Spacer = React.memo(SidebarSpacerComponent); + export const Sidebar = React.memo<{ - filterBy: FilterMode; - setFilterBy: (filterBy: FilterMode) => void; -}>(({ filterBy, setFilterBy }) => { - const apolloClient = useApolloClient(); - const RecentTimelinesFilters = useMemo( - () => , - [filterBy, setFilterBy] - ); - - return ( - - - {RecentTimelinesFilters} - - - - - - - - - void; + setRecentTimelinesFilterBy: (filterBy: RecentTimelinesFilterMode) => void; +}>( + ({ + recentCasesFilterBy, + recentTimelinesFilterBy, + setRecentCasesFilterBy, + setRecentTimelinesFilterBy, + }) => { + const currentUser = useCurrentUser(); + const apolloClient = useApolloClient(); + const recentCasesFilters = useMemo( + () => ( + - - - ); -}); + ), + [currentUser, recentCasesFilterBy, setRecentCasesFilterBy] + ); + const recentCasesFilterOptions = useMemo( + () => + recentCasesFilterBy === 'myRecentlyReported' && currentUser != null + ? { + ...DEFAULT_FILTER_OPTIONS, + reporters: [ + { + email: currentUser.email, + full_name: currentUser.fullName, + username: currentUser.username, + }, + ], + } + : DEFAULT_FILTER_OPTIONS, + [currentUser, recentCasesFilterBy] + ); + const recentTimelinesFilters = useMemo( + () => ( + + ), + [recentTimelinesFilterBy, setRecentTimelinesFilterBy] + ); + + return ( + + + {recentCasesFilters} + + + + + + + {recentTimelinesFilters} + + + + + + + + + + ); + } +); Sidebar.displayName = 'Sidebar'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts b/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts index 5ccd25984bc40..601a629d86e57 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts @@ -26,6 +26,10 @@ export const PAGE_SUBTITLE = i18n.translate('xpack.siem.overview.pageSubtitle', defaultMessage: 'Security Information & Event Management with the Elastic Stack', }); +export const RECENT_CASES = i18n.translate('xpack.siem.overview.recentCasesSidebarTitle', { + defaultMessage: 'Recent cases', +}); + export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimelinesSidebarTitle', { defaultMessage: 'Recent timelines', });