From c16cd12f230046310d9ccc4950316e2033fb8407 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 22 Sep 2020 11:43:05 -0400 Subject: [PATCH 1/7] update @timestamp formatting --- .../resolver/view/panels/event_detail.tsx | 11 ++-- .../resolver/view/panels/node_detail.tsx | 11 ++-- .../view/panels/node_events_of_type.tsx | 5 +- .../public/resolver/view/panels/node_list.tsx | 10 +++- .../view/panels/panel_content_utilities.tsx | 31 ----------- .../view/panels/use_formatted_date.ts | 53 +++++++++++++++++++ 6 files changed, 74 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx index 72f0d54d51fa3..b5ef487beee1f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx @@ -15,7 +15,7 @@ import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { StyledPanel } from '../styles'; -import { BoldCode, StyledTime, GeneratedText, formatDate } from './panel_content_utilities'; +import { BoldCode, StyledTime, GeneratedText } from './panel_content_utilities'; import { Breadcrumbs } from './breadcrumbs'; import * as eventModel from '../../../../common/endpoint/models/event'; import * as selectors from '../../store/selectors'; @@ -25,6 +25,7 @@ import { DescriptiveName } from './descriptive_name'; import { useLinkProps } from '../use_link_props'; import { SafeResolverEvent } from '../../../../common/endpoint/types'; import { deepObjectEntries } from './deep_object_entries'; +import { useFormattedDate } from './use_formatted_date'; export const EventDetail = memo(function EventDetail({ nodeID, @@ -78,12 +79,8 @@ const EventDetailContents = memo(function ({ eventType: string; processEvent: SafeResolverEvent; }) { - const formattedDate = useMemo(() => { - const timestamp = eventModel.timestampSafeVersion(event); - if (timestamp !== undefined) { - return formatDate(new Date(timestamp)); - } - }, [event]); + const timestamp = eventModel.timestampSafeVersion(event); + const formattedDate = useFormattedDate(timestamp); return ( diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx index 04e9de61f6256..181c9ac8ab8a0 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx @@ -16,7 +16,7 @@ import { EuiDescriptionListProps } from '@elastic/eui/src/components/description import { StyledDescriptionList, StyledTitle } from './styles'; import * as selectors from '../../store/selectors'; import * as eventModel from '../../../../common/endpoint/models/event'; -import { formatDate, GeneratedText } from './panel_content_utilities'; +import { GeneratedText } from './panel_content_utilities'; import { Breadcrumbs } from './breadcrumbs'; import { processPath, processPID } from '../../models/process_event'; import { CubeForProcess } from './cube_for_process'; @@ -26,6 +26,7 @@ import { ResolverState } from '../../types'; import { PanelLoading } from './panel_loading'; import { StyledPanel } from '../styles'; import { useLinkProps } from '../use_link_props'; +import { useFormattedDate } from './use_formatted_date'; const StyledCubeForProcess = styled(CubeForProcess)` position: relative; @@ -65,10 +66,10 @@ const NodeDetailView = memo(function ({ const relatedEventTotal = useSelector((state: ResolverState) => { return selectors.relatedEventTotalCount(state)(nodeID); }); - const processInfoEntry: EuiDescriptionListProps['listItems'] = useMemo(() => { - const eventTime = eventModel.eventTimestamp(processEvent); - const dateTime = eventTime === undefined ? null : formatDate(eventTime); + const eventTime = eventModel.eventTimestamp(processEvent); + const dateTime = useFormattedDate(eventTime); + const processInfoEntry: EuiDescriptionListProps['listItems'] = useMemo(() => { const createdEntry = { title: '@timestamp', description: dateTime, @@ -131,7 +132,7 @@ const NodeDetailView = memo(function ({ }); return processDescriptionListData; - }, [processEvent]); + }, [dateTime, processEvent]); const nodesLinkNavProps = useLinkProps({ panelView: 'nodes', diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx index 281794ac24d24..351595a71840d 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx @@ -10,7 +10,7 @@ import { EuiSpacer, EuiText, EuiButtonEmpty, EuiHorizontalRule } from '@elastic/ import { useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { StyledPanel } from '../styles'; -import { formatDate, BoldCode, StyledTime } from './panel_content_utilities'; +import { BoldCode, StyledTime } from './panel_content_utilities'; import { Breadcrumbs } from './breadcrumbs'; import * as eventModel from '../../../../common/endpoint/models/event'; import { SafeResolverEvent } from '../../../../common/endpoint/types'; @@ -19,6 +19,7 @@ import { ResolverState } from '../../types'; import { PanelLoading } from './panel_loading'; import { DescriptiveName } from './descriptive_name'; import { useLinkProps } from '../use_link_props'; +import { useFormattedDate } from './use_formatted_date'; /** * Render a list of events that are related to `nodeID` and that have a category of `eventType`. @@ -83,7 +84,7 @@ const NodeEventsListItem = memo(function ({ eventType: string; }) { const timestamp = eventModel.eventTimestamp(event); - const date = timestamp !== undefined ? formatDate(timestamp) : timestamp; + const date = useFormattedDate(timestamp); const linkProps = useLinkProps({ panelView: 'eventDetail', panelParameters: { diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx index 8fc6e7cc66c79..d206de6ff4436 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx @@ -32,7 +32,6 @@ import { } from './styles'; import * as eventModel from '../../../../common/endpoint/models/event'; import * as selectors from '../../store/selectors'; -import { formatter } from './panel_content_utilities'; import { Breadcrumbs } from './breadcrumbs'; import { CubeForProcess } from './cube_for_process'; import { LimitWarning } from '../limit_warnings'; @@ -41,6 +40,7 @@ import { useLinkProps } from '../use_link_props'; import { useColors } from '../use_colors'; import { SafeResolverEvent } from '../../../../common/endpoint/types'; import { ResolverAction } from '../../store/actions'; +import { useFormattedDate } from './use_formatted_date'; interface ProcessTableView { name?: string; @@ -81,7 +81,7 @@ export const NodeList = memo(() => { sortable: true, render(eventDate?: Date) { return eventDate ? ( - formatter.format(eventDate) + ) : ( {i18n.translate( @@ -220,3 +220,9 @@ function NodeDetailLink({ ); } + +const NodeDetailTimestamp = memo(({ eventDate }: { eventDate: Date }) => { + const formattedDate = useFormattedDate(eventDate); + + return <>{formattedDate}; +}); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx index 5ca34b33b2396..a54326731d812 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx @@ -6,7 +6,6 @@ /* eslint-disable react/display-name */ -import { i18n } from '@kbn/i18n'; import { EuiCode } from '@elastic/eui'; import styled from 'styled-components'; import React, { memo } from 'react'; @@ -59,33 +58,3 @@ export const StyledTime = memo(styled('time')` display: inline-block; text-align: start; `); - -/** - * Long formatter (to second) for DateTime - */ -export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', -}); - -/** - * @returns {string} A nicely formatted string for a date - */ -export function formatDate( - /** To be passed through Date->Intl.DateTimeFormat */ timestamp: ConstructorParameters< - typeof Date - >[0] -): string { - const date = new Date(timestamp); - if (isFinite(date.getTime())) { - return formatter.format(date); - } else { - return i18n.translate('xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate', { - defaultMessage: 'Invalid Date', - }); - } -} diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts new file mode 100644 index 0000000000000..6bf5b02cff578 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts @@ -0,0 +1,53 @@ +/* + * 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'; +import moment from 'moment-timezone'; +import { useUiSetting } from '../../../../../../../src/plugins/kibana_react/public'; + +const invalidDateText = i18n.translate( + 'xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate', + { + defaultMessage: 'Invalid Date', + } +); + +/** + * Long formatter (to second) for DateTime + */ +export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +/** + * + * @description formats a given time based on the user defined format in the advanced settings section of kibana under dateFormat + * @export + * @param {(ConstructorParameters[0] | undefined)} timestamp + * @returns {(string | null)} - Either a formatted date or the text 'Invalid Date' + */ +export function useFormattedDate( + timestamp: ConstructorParameters[0] | Date | undefined +): string { + const dateFormatSetting: string = useUiSetting('dateFormat'); + const timezoneSetting: string = useUiSetting('dateFormat:tz'); + const usableTimezoneSetting = timezoneSetting === 'Browser' ? moment.tz.guess() : timezoneSetting; + + if (timestamp === undefined) return invalidDateText; + + const date = new Date(timestamp); + if (date && Number.isFinite(date.getTime())) { + return dateFormatSetting + ? moment.tz(date, usableTimezoneSetting).format(dateFormatSetting) + : formatter.format(date); + } + + return invalidDateText; +} From cf33fcdf18fc6bf645cfe43d24fbd77e1566b47b Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Wed, 23 Sep 2020 15:00:11 -0400 Subject: [PATCH 2/7] handle missing timestamps --- .../resolver/view/panels/event_detail.tsx | 9 +++++++-- .../view/panels/node_events_of_type.tsx | 4 ++-- .../public/resolver/view/panels/node_list.tsx | 18 ++++-------------- .../view/panels/panel_content_utilities.tsx | 12 ++++++++++++ .../resolver/view/panels/use_formatted_date.ts | 6 +++--- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx index b5ef487beee1f..168752c507d5a 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx @@ -15,7 +15,12 @@ import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { StyledPanel } from '../styles'; -import { BoldCode, StyledTime, GeneratedText } from './panel_content_utilities'; +import { + BoldCode, + StyledTime, + GeneratedText, + noTimestampRetrievedText, +} from './panel_content_utilities'; import { Breadcrumbs } from './breadcrumbs'; import * as eventModel from '../../../../common/endpoint/models/event'; import * as selectors from '../../store/selectors'; @@ -80,7 +85,7 @@ const EventDetailContents = memo(function ({ processEvent: SafeResolverEvent; }) { const timestamp = eventModel.timestampSafeVersion(event); - const formattedDate = useFormattedDate(timestamp); + const formattedDate = useFormattedDate(timestamp) || noTimestampRetrievedText; return ( diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx index 351595a71840d..771a143a9c0cd 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx @@ -10,7 +10,7 @@ import { EuiSpacer, EuiText, EuiButtonEmpty, EuiHorizontalRule } from '@elastic/ import { useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { StyledPanel } from '../styles'; -import { BoldCode, StyledTime } from './panel_content_utilities'; +import { BoldCode, noTimestampRetrievedText, StyledTime } from './panel_content_utilities'; import { Breadcrumbs } from './breadcrumbs'; import * as eventModel from '../../../../common/endpoint/models/event'; import { SafeResolverEvent } from '../../../../common/endpoint/types'; @@ -84,7 +84,7 @@ const NodeEventsListItem = memo(function ({ eventType: string; }) { const timestamp = eventModel.eventTimestamp(event); - const date = useFormattedDate(timestamp); + const date = useFormattedDate(timestamp) || noTimestampRetrievedText; const linkProps = useLinkProps({ panelView: 'eventDetail', panelParameters: { diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx index d206de6ff4436..78d3477301539 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx @@ -41,6 +41,7 @@ import { useColors } from '../use_colors'; import { SafeResolverEvent } from '../../../../common/endpoint/types'; import { ResolverAction } from '../../store/actions'; import { useFormattedDate } from './use_formatted_date'; +import { getEmptyTagValue } from '../../../common/components/empty_value'; interface ProcessTableView { name?: string; @@ -80,18 +81,7 @@ export const NodeList = memo(() => { dataType: 'date', sortable: true, render(eventDate?: Date) { - return eventDate ? ( - - ) : ( - - {i18n.translate( - 'xpack.securitySolution.endpoint.resolver.panel.table.row.timestampInvalidLabel', - { - defaultMessage: 'invalid', - } - )} - - ); + return ; }, }, ], @@ -221,8 +211,8 @@ function NodeDetailLink({ ); } -const NodeDetailTimestamp = memo(({ eventDate }: { eventDate: Date }) => { +const NodeDetailTimestamp = memo(({ eventDate }: { eventDate: Date | undefined }) => { const formattedDate = useFormattedDate(eventDate); - return <>{formattedDate}; + return formattedDate ? <>{formattedDate} : getEmptyTagValue(); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx index a54326731d812..a20498cbfb67b 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx @@ -7,9 +7,21 @@ /* eslint-disable react/display-name */ import { EuiCode } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import React, { memo } from 'react'; +/** + * Text to use in place of an undefined timestamp value + */ + +export const noTimestampRetrievedText = i18n.translate( + 'xpack.securitySolution.enpdoint.resolver.panelutils.noTimestampRetrieved', + { + defaultMessage: 'No timestamp retrieved', + } +); + /** * A bold version of EuiCode to display certain titles with */ diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts index 6bf5b02cff578..05e7154dd6fdd 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.ts @@ -17,7 +17,7 @@ const invalidDateText = i18n.translate( /** * Long formatter (to second) for DateTime */ -export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { +const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { year: 'numeric', month: '2-digit', day: '2-digit', @@ -35,12 +35,12 @@ export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { */ export function useFormattedDate( timestamp: ConstructorParameters[0] | Date | undefined -): string { +): string | undefined { const dateFormatSetting: string = useUiSetting('dateFormat'); const timezoneSetting: string = useUiSetting('dateFormat:tz'); const usableTimezoneSetting = timezoneSetting === 'Browser' ? moment.tz.guess() : timezoneSetting; - if (timestamp === undefined) return invalidDateText; + if (!timestamp) return undefined; const date = new Date(timestamp); if (date && Number.isFinite(date.getTime())) { From 3d1a42a517a9cc9a036245ecea2eda25060e991a Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Wed, 23 Sep 2020 15:28:04 -0400 Subject: [PATCH 3/7] remove unused translation --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 42e695788448f..7159c794abbb5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15885,7 +15885,6 @@ "xpack.securitySolution.endpoint.resolver.panel.table.row.count": "カウント", "xpack.securitySolution.endpoint.resolver.panel.table.row.eventType": "イベントタイプ", "xpack.securitySolution.endpoint.resolver.panel.table.row.processNameTitle": "プロセス名", - "xpack.securitySolution.endpoint.resolver.panel.table.row.timestampInvalidLabel": "無効", "xpack.securitySolution.endpoint.resolver.panel.table.row.timestampTitle": "タイムスタンプ", "xpack.securitySolution.endpoint.resolver.panel.table.row.valueMissingDescription": "値が見つかりません", "xpack.securitySolution.endpoint.resolver.relatedEventLimitExceeded": "{numberOfEventsMissing} {category}件のイベントを表示できませんでした。データの上限に達しました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 394acbf65d1b5..d13cffa339e8d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15895,7 +15895,6 @@ "xpack.securitySolution.endpoint.resolver.panel.table.row.count": "计数", "xpack.securitySolution.endpoint.resolver.panel.table.row.eventType": "事件类型", "xpack.securitySolution.endpoint.resolver.panel.table.row.processNameTitle": "进程名称", - "xpack.securitySolution.endpoint.resolver.panel.table.row.timestampInvalidLabel": "无效", "xpack.securitySolution.endpoint.resolver.panel.table.row.timestampTitle": "时间戳", "xpack.securitySolution.endpoint.resolver.panel.table.row.valueMissingDescription": "值缺失", "xpack.securitySolution.endpoint.resolver.relatedEventLimitExceeded": "{numberOfEventsMissing} 个{category}事件无法显示,因为已达到数据限制。", From c48611443e547874bbdb94a3ac9ab62b0e18baf7 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Thu, 24 Sep 2020 10:32:30 -0400 Subject: [PATCH 4/7] add tests for date --- .../public/resolver/mocks/get_ui_settings.ts | 14 +++ .../public/resolver/mocks/resolver_tree.ts | 40 +++---- .../test_utilities/simulator/index.tsx | 11 +- .../public/resolver/view/panel.test.tsx | 3 + .../view/panels/use_formatted_date.test.tsx | 102 ++++++++++++++++++ 5 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts create mode 100644 x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts b/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts new file mode 100644 index 0000000000000..21f9a7bde1309 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts @@ -0,0 +1,14 @@ +/* + * 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 function getUiSettings(key: string): string { + if (key === 'dateFormat') { + return 'MMM D, YYYY @ HH:mm:ss.SSS'; + } + if (key === 'dateFormat:tz') { + return 'America/New_York'; + } +} diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts index 8691ecac4d1cc..3f7c58efc762b 100644 --- a/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts @@ -21,19 +21,19 @@ export function mockTreeWith2AncestorsAndNoChildren({ entityID: secondAncestorID, processName: 'a', parentEntityID: 'none', - timestamp: 0, + timestamp: 1600863932316, }); const firstAncestor: SafeResolverEvent = mockEndpointEvent({ entityID: firstAncestorID, processName: 'b', parentEntityID: secondAncestorID, - timestamp: 1, + timestamp: 1600863932317, }); const originEvent: SafeResolverEvent = mockEndpointEvent({ entityID: originID, processName: 'c', parentEntityID: firstAncestorID, - timestamp: 2, + timestamp: 1600863932318, }); return { entityID: originID, @@ -68,39 +68,39 @@ export function mockTreeWithAllProcessesTerminated({ entityID: secondAncestorID, processName: 'a', parentEntityID: 'none', - timestamp: 0, + timestamp: 1600863932316, }); const firstAncestor: SafeResolverEvent = mockEndpointEvent({ entityID: firstAncestorID, processName: 'b', parentEntityID: secondAncestorID, - timestamp: 1, + timestamp: 1600863932317, }); const originEvent: SafeResolverEvent = mockEndpointEvent({ entityID: originID, processName: 'c', parentEntityID: firstAncestorID, - timestamp: 2, + timestamp: 1600863932318, }); const secondAncestorTermination: SafeResolverEvent = mockEndpointEvent({ entityID: secondAncestorID, processName: 'a', parentEntityID: 'none', - timestamp: 0, + timestamp: 1600863932316, eventType: 'end', }); const firstAncestorTermination: SafeResolverEvent = mockEndpointEvent({ entityID: firstAncestorID, processName: 'b', parentEntityID: secondAncestorID, - timestamp: 1, + timestamp: 1600863932317, eventType: 'end', }); const originEventTermination: SafeResolverEvent = mockEndpointEvent({ entityID: originID, processName: 'c', parentEntityID: firstAncestorID, - timestamp: 2, + timestamp: 1600863932318, eventType: 'end', }); return ({ @@ -162,21 +162,21 @@ export function mockTreeWithNoAncestorsAnd2Children({ entityID: originID, processName: 'c.ext', parentEntityID: 'none', - timestamp: 0, + timestamp: 1600863932316, }); const firstChild: SafeResolverEvent = mockEndpointEvent({ pid: 1, entityID: firstChildID, processName: 'd', parentEntityID: originID, - timestamp: 1, + timestamp: 1600863932317, }); const secondChild: SafeResolverEvent = mockEndpointEvent({ pid: 2, entityID: secondChildID, processName: 'e', parentEntityID: originID, - timestamp: 2, + timestamp: 1600863932318, }); return { @@ -216,50 +216,50 @@ export function mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents const ancestor: SafeResolverEvent = mockEndpointEvent({ entityID: ancestorID, processName: ancestorID, - timestamp: 1, + timestamp: 1600863932317, parentEntityID: undefined, }); const ancestorClone: SafeResolverEvent = mockEndpointEvent({ entityID: ancestorID, processName: ancestorID, - timestamp: 1, + timestamp: 1600863932317, parentEntityID: undefined, }); const origin: SafeResolverEvent = mockEndpointEvent({ entityID: originID, processName: originID, parentEntityID: ancestorID, - timestamp: 0, + timestamp: 1600863932316, }); const originClone: SafeResolverEvent = mockEndpointEvent({ entityID: originID, processName: originID, parentEntityID: ancestorID, - timestamp: 0, + timestamp: 1600863932316, }); const firstChild: SafeResolverEvent = mockEndpointEvent({ entityID: firstChildID, processName: firstChildID, parentEntityID: originID, - timestamp: 1, + timestamp: 1600863932317, }); const firstChildClone: SafeResolverEvent = mockEndpointEvent({ entityID: firstChildID, processName: firstChildID, parentEntityID: originID, - timestamp: 1, + timestamp: 1600863932317, }); const secondChild: SafeResolverEvent = mockEndpointEvent({ entityID: secondChildID, processName: secondChildID, parentEntityID: originID, - timestamp: 2, + timestamp: 1600863932318, }); const secondChildClone: SafeResolverEvent = mockEndpointEvent({ entityID: secondChildID, processName: secondChildID, parentEntityID: originID, - timestamp: 2, + timestamp: 1600863932318, }); return ({ diff --git a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx index 9d10d1c2b64a7..ea603f2583431 100644 --- a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { Store, createStore, applyMiddleware } from 'redux'; import { mount, ReactWrapper } from 'enzyme'; import { History as HistoryPackageHistoryInterface, createMemoryHistory } from 'history'; -import { CoreStart } from '../../../../../../../src/core/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { spyMiddlewareFactory } from '../spy_middleware_factory'; import { resolverMiddlewareFactory } from '../../store/middleware'; @@ -17,6 +16,7 @@ import { MockResolver } from './mock_resolver'; import { ResolverState, DataAccessLayer, SpyMiddleware, SideEffectSimulator } from '../../types'; import { ResolverAction } from '../../store/actions'; import { sideEffectSimulatorFactory } from '../../view/side_effect_simulator_factory'; +import { getUiSettings } from '../../mocks/get_ui_settings'; /** * Test a Resolver instance using jest, enzyme, and a mock data layer. @@ -91,7 +91,9 @@ export class Simulator { this.history = history ?? createMemoryHistory(); // Used for `KibanaContextProvider` - const coreStart: CoreStart = coreMock.createStart(); + const coreStart = coreMock.createStart(); + + coreStart.uiSettings.get.mockImplementation(getUiSettings); this.sideEffectSimulator = sideEffectSimulatorFactory(); @@ -296,10 +298,7 @@ export class Simulator { const title = titles.at(index).text(); const description = descriptions.at(index).text(); - // Exclude timestamp since we can't currently calculate the expected description for it from tests - if (title !== '@timestamp') { - entries.push([title, description]); - } + entries.push([title, description]); } return entries; } diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx index 2f23469606aca..63a70716b2d41 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx @@ -88,6 +88,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and title: 'c.ext', titleIcon: 'Running Process', detailEntries: [ + ['@timestamp', 'Sep 23, 2020 @ 08:25:32.316'], ['process.executable', 'executable'], ['process.pid', '0'], ['user.name', 'user.name'], @@ -128,6 +129,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and await expect( simulator().map(() => simulator().nodeDetailDescriptionListEntries()) ).toYieldEqualTo([ + ['@timestamp', 'Sep 23, 2020 @ 08:25:32.317'], ['process.executable', 'executable'], ['process.pid', '1'], ['user.name', 'user.name'], @@ -168,6 +170,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and await expect( simulator().map(() => simulator().nodeDetailDescriptionListEntries()) ).toYieldEqualTo([ + ['@timestamp', 'Sep 23, 2020 @ 08:25:32.316'], ['process.executable', 'executable'], ['process.pid', '0'], ['user.name', 'user.name'], diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx new file mode 100644 index 0000000000000..16a68902e5c83 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 React from 'react'; +import { render, RenderResult } from '@testing-library/react'; +import { useFormattedDate } from './use_formatted_date'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { getUiSettings } from '../../mocks/get_ui_settings'; + +describe('useFormattedDate', () => { + let element: HTMLElement; + const testID = 'formattedDate'; + let reactRenderResult: RenderResult; + + beforeEach(async () => { + const mockCoreStart = coreMock.createStart(); + mockCoreStart.uiSettings.get.mockImplementation(getUiSettings); + + function Test({ date }: { date: string | number | undefined }) { + const formattedDate = useFormattedDate(date); + return
{formattedDate}
; + } + + reactRenderResult = (date: string | number | undefined) => + render( + + + + ); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('when the provided date is undefined', () => { + it('should return undefined', async () => { + const { findByTestId } = reactRenderResult(undefined); + element = await findByTestId(testID); + + expect(element).toBeEmpty(); + }); + }); + + describe('when the provided date is empty', () => { + it('should return undefined', async () => { + const { findByTestId } = reactRenderResult(''); + element = await findByTestId(testID); + + expect(element).toBeEmpty(); + }); + }); + + describe('when the provided date is an invalid date', () => { + it('should return the string invalid date', async () => { + const { findByTestId } = reactRenderResult('randomString'); + element = await findByTestId(testID); + + expect(element).toHaveTextContent('Invalid Date'); + }); + }); + + describe('when the provided date is a stringified unix timestamp', () => { + it('should return the string invalid date', async () => { + const { findByTestId } = reactRenderResult('1600863932316'); + element = await findByTestId(testID); + + expect(element).toHaveTextContent('Invalid Date'); + }); + }); + + describe('when the provided date is a valid numerical timestamp', () => { + it('should return the string invalid date', async () => { + const { findByTestId } = reactRenderResult(1600863932316); + element = await findByTestId(testID); + + expect(element).toHaveTextContent('Sep 23, 2020 @ 08:25:32.316'); + }); + }); + + describe('when the provided date is a date string', () => { + it('should return the string invalid date', async () => { + const { findByTestId } = reactRenderResult('2020-09-23T12:25:32Z'); + element = await findByTestId(testID); + + expect(element).toHaveTextContent('Sep 23, 2020 @ 08:25:32.000'); + }); + }); + + describe('when the provided date is a valid date', () => { + it('should return the string invalid date', async () => { + const validDate = new Date(1600863932316); + const { findByTestId } = reactRenderResult(validDate); + element = await findByTestId(testID); + + expect(element).toHaveTextContent('Sep 23, 2020 @ 08:25:32.316'); + }); + }); +}); From 155b82c7910f276e489d1d65afdadbb508da6076 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Thu, 24 Sep 2020 11:09:25 -0400 Subject: [PATCH 5/7] types fix --- .../security_solution/public/resolver/mocks/get_ui_settings.ts | 2 +- .../public/resolver/view/panels/use_formatted_date.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts b/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts index 21f9a7bde1309..ab1a5c86859ac 100644 --- a/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts +++ b/x-pack/plugins/security_solution/public/resolver/mocks/get_ui_settings.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export function getUiSettings(key: string): string { +export function getUiSettings(key: string): string | undefined { if (key === 'dateFormat') { return 'MMM D, YYYY @ HH:mm:ss.SSS'; } diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx index 16a68902e5c83..0818fa03e6d21 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx @@ -25,7 +25,7 @@ describe('useFormattedDate', () => { return
{formattedDate}
; } - reactRenderResult = (date: string | number | undefined) => + reactRenderResult = (date: string | number | undefined): RenderResult => render( From d22463c3436a90ae677399a0b866db1d2edc81b3 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Thu, 24 Sep 2020 11:24:17 -0400 Subject: [PATCH 6/7] types fix --- .../public/resolver/view/panels/use_formatted_date.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx index 0818fa03e6d21..b5ebdb950ba90 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx @@ -14,7 +14,7 @@ import { getUiSettings } from '../../mocks/get_ui_settings'; describe('useFormattedDate', () => { let element: HTMLElement; const testID = 'formattedDate'; - let reactRenderResult: RenderResult; + let reactRenderResult: (date: string | number | undefined) => RenderResult; beforeEach(async () => { const mockCoreStart = coreMock.createStart(); From a5f336a65ecbb317c28e9bc4a26d396b299e69e8 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Thu, 24 Sep 2020 11:45:19 -0400 Subject: [PATCH 7/7] types fix --- .../resolver/view/panels/use_formatted_date.test.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx index b5ebdb950ba90..9e9ae26900efa 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/use_formatted_date.test.tsx @@ -14,18 +14,22 @@ import { getUiSettings } from '../../mocks/get_ui_settings'; describe('useFormattedDate', () => { let element: HTMLElement; const testID = 'formattedDate'; - let reactRenderResult: (date: string | number | undefined) => RenderResult; + let reactRenderResult: ( + date: ConstructorParameters[0] | Date | undefined + ) => RenderResult; beforeEach(async () => { const mockCoreStart = coreMock.createStart(); mockCoreStart.uiSettings.get.mockImplementation(getUiSettings); - function Test({ date }: { date: string | number | undefined }) { + function Test({ date }: { date: ConstructorParameters[0] | Date | undefined }) { const formattedDate = useFormattedDate(date); return
{formattedDate}
; } - reactRenderResult = (date: string | number | undefined): RenderResult => + reactRenderResult = ( + date: ConstructorParameters[0] | Date | undefined + ): RenderResult => render(