Skip to content

Commit

Permalink
[SIEM] Table columns, number related design tweaks (#48969)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic authored Oct 24, 2019
1 parent f601ab4 commit 39aa439
Show file tree
Hide file tree
Showing 18 changed files with 135 additions and 77 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,14 @@ describe('Field Renderers', () => {
describe('#dateRenderer', () => {
test('it renders correctly against snapshot', () => {
const wrapper = shallow(
<TestProviders>{dateRenderer('firstSeen', mockData.complete.source!)}</TestProviders>
<TestProviders>{dateRenderer(mockData.complete.source!.firstSeen)}</TestProviders>
);

expect(toJson(wrapper)).toMatchSnapshot();
});

test('it renders emptyTagValue when invalid field provided', () => {
const wrapper = mount(
<TestProviders>{dateRenderer('geo.spark_plug', mockData.complete.source!)}</TestProviders>
);
const wrapper = mount(<TestProviders>{dateRenderer(null)}</TestProviders>);
expect(wrapper.text()).toEqual(getEmptyValue());
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ import React, { Fragment, useState } from 'react';
import { pure } from 'recompose';

import styled from 'styled-components';
import {
AutonomousSystem,
FlowTarget,
HostEcsFields,
IpOverviewData,
Overview,
} from '../../graphql/types';
import { AutonomousSystem, FlowTarget, HostEcsFields, IpOverviewData } from '../../graphql/types';
import { escapeDataProviderId } from '../drag_and_drop/helpers';
import { DefaultDraggable } from '../draggables';
import { getEmptyTagValue } from '../empty_value';
import { FormattedDate } from '../formatted_date';
import { FormattedRelativePreferenceDate } from '../formatted_date';
import { HostDetailsLink, ReputationLink, VirusTotalLink, WhoIsLink } from '../links';
import { Spacer } from '../page';
import * as i18n from '../page/network/ip_overview/translations';
Expand Down Expand Up @@ -58,8 +52,8 @@ export const locationRenderer = (fieldNames: string[], data: IpOverviewData): Re
getEmptyTagValue()
);

export const dateRenderer = (fieldName: string, data: Overview): React.ReactElement => (
<FormattedDate value={getOr(null, fieldName, data)} fieldName={fieldName} />
export const dateRenderer = (timestamp?: string | null): React.ReactElement => (
<FormattedRelativePreferenceDate value={timestamp} />
);

export const autonomousSystemRenderer = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';

import { mockFrameworks, TestProviders, MockFrameworks, getMockKibanaUiSetting } from '../../mock';

import { PreferenceFormattedDate, FormattedDate } from '.';
import { getEmptyValue } from '../empty_value';
import { PreferenceFormattedDate, FormattedDate, FormattedRelativePreferenceDate } from '.';
import { getEmptyString, getEmptyValue } from '../empty_value';

const mockUseKibanaUiSetting: jest.Mock = useKibanaUiSetting as jest.Mock;
jest.mock('../../lib/settings/use_kibana_ui_setting', () => ({
Expand Down Expand Up @@ -162,4 +162,45 @@ describe('formatted_date', () => {
});
});
});

describe('FormattedRelativePreferenceDate', () => {
describe('rendering', () => {
test('renders time over an hour correctly against snapshot', () => {
const isoDateString = '2019-02-25T22:27:05.000Z';
const wrapper = shallow(<FormattedRelativePreferenceDate value={isoDateString} />);
expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true);
});
test('renders time under an hour correctly against snapshot', () => {
const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString();
const wrapper = shallow(<FormattedRelativePreferenceDate value={timeTwelveMinutesAgo} />);
expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true);
});
test('renders empty string value correctly', () => {
const wrapper = mount(
<TestProviders>
<FormattedRelativePreferenceDate value={''} />
</TestProviders>
);
expect(wrapper.text()).toBe(getEmptyString());
});

test('renders undefined value correctly', () => {
const wrapper = mount(
<TestProviders>
<FormattedRelativePreferenceDate />
</TestProviders>
);
expect(wrapper.text()).toBe(getEmptyValue());
});

test('renders null value correctly', () => {
const wrapper = mount(
<TestProviders>
<FormattedRelativePreferenceDate value={null} />
</TestProviders>
);
expect(wrapper.text()).toBe(getEmptyValue());
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import moment from 'moment-timezone';
import * as React from 'react';
import { FormattedRelative } from '@kbn/i18n/react';
import { pure } from 'recompose';

import {
Expand Down Expand Up @@ -62,3 +63,36 @@ export const FormattedDate = pure<{
);

FormattedDate.displayName = 'FormattedDate';

/**
* Renders the specified date value according to under/over one hour
* Under an hour = relative format
* Over an hour = in a format determined by the user's preferences,
* with a tooltip that renders:
* - the name of the field
* - a humanized relative date (e.g. 16 minutes ago)
* - a long representation of the date that includes the day of the week (e.g. Thursday, March 21, 2019 6:47pm)
* - the raw date value (e.g. 2019-03-22T00:47:46Z)
*/

export const FormattedRelativePreferenceDate = ({ value }: { value?: string | number | null }) => {
if (value == null) {
return getOrEmptyTagFromValue(value);
}
const maybeDate = getMaybeDate(value);
if (!maybeDate.isValid()) {
return getOrEmptyTagFromValue(value);
}
const date = maybeDate.toDate();
return (
<LocalizedDateTooltip date={date}>
{moment(date)
.add(1, 'hours')
.isBefore(new Date()) ? (
<PreferenceFormattedDate data-test-subj="preference-time" value={date} />
) : (
<FormattedRelative data-test-subj="relative-time" value={date} />
)}
</LocalizedDateTooltip>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ describe('Last Event Time Stat', () => {
<LastEventTime indexKey={LastEventIndexKey.hosts} />
</TestProviders>
);

expect(wrapper.html()).toBe('<span class="euiToolTipAnchor">Last event: 12 days ago</span>');
expect(wrapper.html()).toBe('Last event: <span class="euiToolTipAnchor">12 minutes ago</span>');
});
test('Bad date time string', async () => {
mockUseLastEventTimeQuery.mockImplementation(() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
*/

import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { memo } from 'react';

import { LastEventIndexKey } from '../../graphql/types';
import { useLastEventTimeQuery } from '../../containers/events/last_event_time';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';

export interface LastEventTimeProps {
hostName?: string;
indexKey: LastEventIndexKey;
ip?: string;
}

export const LastEventTime = memo<LastEventTimeProps>(({ hostName, indexKey, ip }) => {
const { loading, lastSeen, errorMessage } = useLastEventTimeQuery(
indexKey,
Expand All @@ -37,22 +39,21 @@ export const LastEventTime = memo<LastEventTimeProps>(({ hostName, indexKey, ip
</EuiToolTip>
);
}

return (
<>
{loading && <EuiLoadingSpinner size="m" />}
{!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date'
? lastSeen
: !loading &&
lastSeen != null && (
<EuiToolTip data-test-subj="last_event_time" position="bottom" content={lastSeen}>
<FormattedMessage
id="xpack.siem.headerPage.pageSubtitle"
defaultMessage="Last event: {beat}"
values={{
beat: <FormattedRelative value={new Date(lastSeen)} />,
}}
/>
</EuiToolTip>
<FormattedMessage
id="xpack.siem.headerPage.pageSubtitle"
defaultMessage="Last event: {beat}"
values={{
beat: <FormattedRelativePreferenceDate value={lastSeen} />,
}}
/>
)}
{!loading && lastSeen == null && getEmptyTagValue()}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import moment from 'moment';
import { Columns } from '../../paginated_table';
import { AnomaliesByHost, Anomaly, NarrowDateRange } from '../types';
import { getRowItemDraggable } from '../../tables/helpers';
Expand All @@ -18,10 +17,9 @@ import * as i18n from './translations';
import { getEntries } from '../get_entries';
import { DraggableScore } from '../score/draggable_score';
import { createExplorerLink } from '../links/create_explorer_link';
import { LocalizedDateTooltip } from '../../localized_date_tooltip';
import { PreferenceFormattedDate } from '../../formatted_date';
import { HostsType } from '../../../store/hosts/model';
import { escapeDataProviderId } from '../../drag_and_drop/helpers';
import { FormattedRelativePreferenceDate } from '../../formatted_date';

export const getAnomaliesHostTableColumns = (
startDate: number,
Expand Down Expand Up @@ -126,11 +124,7 @@ export const getAnomaliesHostTableColumns = (
name: i18n.TIME_STAMP,
field: 'anomaly.time',
sortable: true,
render: time => (
<LocalizedDateTooltip date={moment(new Date(time)).toDate()}>
<PreferenceFormattedDate value={new Date(time)} />
</LocalizedDateTooltip>
),
render: time => <FormattedRelativePreferenceDate value={time} />,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import moment from 'moment';

import { Columns } from '../../paginated_table';
import { Anomaly, NarrowDateRange, AnomaliesByNetwork } from '../types';
import { getRowItemDraggable } from '../../tables/helpers';
Expand All @@ -18,8 +18,7 @@ import * as i18n from './translations';
import { getEntries } from '../get_entries';
import { DraggableScore } from '../score/draggable_score';
import { createExplorerLink } from '../links/create_explorer_link';
import { LocalizedDateTooltip } from '../../localized_date_tooltip';
import { PreferenceFormattedDate } from '../../formatted_date';
import { FormattedRelativePreferenceDate } from '../../formatted_date';
import { NetworkType } from '../../../store/network/model';
import { escapeDataProviderId } from '../../drag_and_drop/helpers';

Expand Down Expand Up @@ -120,11 +119,7 @@ export const getAnomaliesNetworkTableColumns = (
name: i18n.TIME_STAMP,
field: 'anomaly.time',
sortable: true,
render: time => (
<LocalizedDateTooltip date={moment(new Date(time)).toDate()}>
<PreferenceFormattedDate value={new Date(time)} />
</LocalizedDateTooltip>
),
render: time => <FormattedRelativePreferenceDate value={time} />,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { NotePreviews } from '../note_previews';
import * as i18n from '../translations';
import { OnOpenTimeline, OnToggleShowNotes, OpenTimelineResult } from '../types';
import { getEmptyTagValue } from '../../empty_value';
import { FormattedDate } from '../../formatted_date';
import { FormattedRelativePreferenceDate } from '../../formatted_date';

/**
* Returns the column definitions (passed as the `columns` prop to
Expand Down Expand Up @@ -96,7 +96,7 @@ export const getCommonColumns = ({
render: (date: number, timelineResult: OpenTimelineResult) => (
<div data-test-subj="updated">
{timelineResult.updated != null ? (
<FormattedDate fieldName="" value={date} />
<FormattedRelativePreferenceDate value={date} />
) : (
getEmptyTagValue()
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiToolTip } from '@elastic/eui';
import { FormattedRelative } from '@kbn/i18n/react';
import { has } from 'lodash/fp';
import React from 'react';
import { connect } from 'react-redux';
Expand All @@ -18,6 +16,7 @@ import { hostsModel, hostsSelectors, State } from '../../../../store';
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { getEmptyTagValue } from '../../../empty_value';
import { FormattedRelativePreferenceDate } from '../../../formatted_date';
import { HostDetailsLink, IPDetailsLink } from '../../../links';
import { Columns, ItemsPerRow, PaginatedTable } from '../../../paginated_table';
import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider';
Expand Down Expand Up @@ -200,6 +199,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
/>
);
},
width: '8%',
},
{
name: i18n.FAILURES,
Expand Down Expand Up @@ -237,16 +237,15 @@ const getAuthenticationColumns = (): AuthTableColumns => [
/>
);
},
width: '8%',
},
{
name: i18n.LAST_SUCCESSFUL_TIME,
truncateText: false,
hideForMobile: false,
render: ({ node }) =>
has('lastSuccess.timestamp', node) ? (
<EuiToolTip position="bottom" content={node.lastSuccess!.timestamp!}>
<FormattedRelative value={new Date(node.lastSuccess!.timestamp!)} />
</EuiToolTip>
has('lastSuccess.timestamp', node) && node.lastSuccess!.timestamp != null ? (
<FormattedRelativePreferenceDate value={node.lastSuccess!.timestamp} />
) : (
getEmptyTagValue()
),
Expand Down Expand Up @@ -291,9 +290,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
hideForMobile: false,
render: ({ node }) =>
has('lastFailure.timestamp', node) && node.lastFailure!.timestamp != null ? (
<EuiToolTip position="bottom" content={node.lastFailure!.timestamp!}>
<FormattedRelative value={new Date(node.lastFailure!.timestamp!)} />
</EuiToolTip>
<FormattedRelativePreferenceDate value={node.lastFailure!.timestamp} />
) : (
getEmptyTagValue()
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
*/

import { EuiIcon, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui';
import moment from 'moment';

import React from 'react';
import { ApolloConsumer } from 'react-apollo';
import { pure } from 'recompose';

import { useFirstLastSeenHostQuery } from '../../../../containers/hosts/first_last_seen';
import { getEmptyTagValue } from '../../../empty_value';
import { PreferenceFormattedDate } from '../../../formatted_date';
import { LocalizedDateTooltip } from '../../../localized_date_tooltip';
import { FormattedRelativePreferenceDate } from '../../../formatted_date';

export enum FirstLastSeenHostType {
FIRST_SEEN = 'first-seen',
Expand Down Expand Up @@ -52,9 +51,7 @@ export const FirstLastSeenHost = pure<{ hostname: string; type: FirstLastSeenHos
: !loading &&
valueSeen != null && (
<EuiText size="s">
<LocalizedDateTooltip date={moment(new Date(valueSeen)).toDate()}>
<PreferenceFormattedDate value={new Date(valueSeen)} />
</LocalizedDateTooltip>
<FormattedRelativePreferenceDate value={`${valueSeen}`} />
</EuiText>
)}
{!loading && valueSeen == null && getEmptyTagValue()}
Expand Down
Loading

0 comments on commit 39aa439

Please sign in to comment.