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[] }) => (
+
+ ),
+ 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[] }) => (
-
- ),
- 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',
});