Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Security Solution][Resolver] Update @timestamp formatting #78166

Merged
merged 9 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 | undefined {
if (key === 'dateFormat') {
return 'MMM D, YYYY @ HH:mm:ss.SSS';
}
if (key === 'dateFormat:tz') {
return 'America/New_York';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 ({
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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.
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -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'],
Expand Down Expand Up @@ -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'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, formatDate } 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';
Expand All @@ -25,6 +30,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,
Expand Down Expand Up @@ -78,12 +84,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) || noTimestampRetrievedText;

return (
<StyledPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -131,7 +132,7 @@ const NodeDetailView = memo(function ({
});

return processDescriptionListData;
}, [processEvent]);
}, [dateTime, processEvent]);

const nodesLinkNavProps = useLinkProps({
panelView: 'nodes',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, noTimestampRetrievedText, StyledTime } from './panel_content_utilities';
import { Breadcrumbs } from './breadcrumbs';
import * as eventModel from '../../../../common/endpoint/models/event';
import { SafeResolverEvent } from '../../../../common/endpoint/types';
Expand All @@ -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`.
Expand Down Expand Up @@ -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) || noTimestampRetrievedText;
const linkProps = useLinkProps({
panelView: 'eventDetail',
panelParameters: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -41,6 +40,8 @@ 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';
import { getEmptyTagValue } from '../../../common/components/empty_value';

interface ProcessTableView {
name?: string;
Expand Down Expand Up @@ -80,18 +81,7 @@ export const NodeList = memo(() => {
dataType: 'date',
sortable: true,
render(eventDate?: Date) {
return eventDate ? (
formatter.format(eventDate)
) : (
<EuiBadge color="warning">
{i18n.translate(
'xpack.securitySolution.endpoint.resolver.panel.table.row.timestampInvalidLabel',
{
defaultMessage: 'invalid',
}
)}
</EuiBadge>
);
return <NodeDetailTimestamp eventDate={eventDate} />;
},
},
],
Expand Down Expand Up @@ -220,3 +210,9 @@ function NodeDetailLink({
</EuiButtonEmpty>
);
}

const NodeDetailTimestamp = memo(({ eventDate }: { eventDate: Date | undefined }) => {
const formattedDate = useFormattedDate(eventDate);

return formattedDate ? <>{formattedDate}</> : getEmptyTagValue();
});
Loading