From a6903793bda1921df8b5375c0787ef36dce19680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez?= Date: Thu, 17 Oct 2019 16:13:35 +0200 Subject: [PATCH] [Logs UI] Use short timestamps in the log stream view (#47042) This commit changes the appearance of the log stream page. It removes the date value from the timestamp column, making it shorter and giving more space for the message of the log When the date of two log entries is different, we add a marker to indicate this change. The date of the first visible log item is written in the header of the table. --- .../public/components/formatted_time.tsx | 21 ++++- .../log_text_stream/column_headers.tsx | 25 ++++-- .../logging/log_text_stream/log_date_row.tsx | 32 ++++++++ .../log_entry_timestamp_column.tsx | 5 +- .../scrollable_log_text_stream_view.tsx | 76 +++++++++++-------- .../infra/public/utils/formatters/datetime.ts | 17 +++++ .../apps/infra/logs_source_configuration.ts | 4 +- 7 files changed, 133 insertions(+), 47 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx create mode 100644 x-pack/legacy/plugins/infra/public/utils/formatters/datetime.ts diff --git a/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx b/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx index f6a6545920fc5..78255c55df124 100644 --- a/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx +++ b/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx @@ -17,8 +17,25 @@ const getFormattedTime = ( return userFormat ? moment(time).format(userFormat) : moment(time).format(fallbackFormat); }; -export const useFormattedTime = (time: number, fallbackFormat?: string) => { - const [dateFormat] = useKibanaUiSetting('dateFormat'); +interface UseFormattedTimeOptions { + format?: 'dateTime' | 'time'; + fallbackFormat?: string; +} + +export const useFormattedTime = ( + time: number, + { format = 'dateTime', fallbackFormat }: UseFormattedTimeOptions = {} +) => { + // `dateFormat:scaled` is an array of `[key, format]` tuples. + // The hook might return `undefined`, so use a sane default for the `find` later. + const scaledTuples = useKibanaUiSetting('dateFormat:scaled')[0] || [['', undefined]]; + + const formatMap = { + dateTime: useKibanaUiSetting('dateFormat')[0], + time: scaledTuples.find(([key]: [string, string]) => key === '')[1], + }; + + const dateFormat = formatMap[format]; const formattedTime = useMemo(() => getFormattedTime(time, dateFormat, fallbackFormat), [ getFormattedTime, time, diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx index 3d78c8d728fc6..56a84d258c907 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { transparentize } from 'polished'; import euiStyled from '../../../../../../common/eui_styled_components'; import { @@ -20,6 +21,8 @@ import { LogEntryColumnWidths, } from './log_entry_column'; import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel'; +import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { localizedDate } from '../../../utils/formatters/datetime'; export const LogColumnHeaders: React.FunctionComponent<{ columnConfigurations: LogColumnConfiguration[]; @@ -30,13 +33,16 @@ export const LogColumnHeaders: React.FunctionComponent<{ {columnConfigurations.map(columnConfiguration => { if (isTimestampLogColumnConfiguration(columnConfiguration)) { return ( - - Timestamp - + + {({ firstVisiblePosition }) => ( + + {firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'} + + )} + ); } else if (isMessageLogColumnConfiguration(columnConfiguration)) { return ( @@ -83,13 +89,16 @@ const LogColumnHeadersWrapper = euiStyled.div.attrs({ justify-content: flex-start; overflow: hidden; padding-right: ${ASSUMED_SCROLLBAR_WIDTH}px; + border-bottom: ${props => props.theme.eui.euiBorderThin}; + box-shadow: 0 2px 2px -1px ${props => transparentize(0.3, props.theme.eui.euiColorLightShade)}; + position: relative; + z-index: 1; `; const LogColumnHeaderWrapper = LogEntryColumn.extend.attrs({ role: 'columnheader', })` align-items: center; - border-bottom: ${props => props.theme.eui.euiBorderThick}; display: flex; flex-direction: row; height: 32px; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx new file mode 100644 index 0000000000000..fbc450950b828 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx @@ -0,0 +1,32 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; +import { localizedDate } from '../../../utils/formatters/datetime'; + +interface LogDateRowProps { + timestamp: number; +} + +/** + * Show a row with the date in the log stream + */ +export const LogDateRow: React.FC = ({ timestamp }) => { + const formattedDate = localizedDate(timestamp); + + return ( + + + +

{formattedDate}

+
+
+ + + +
+ ); +}; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx index c996342d0c060..884e5ff0a5bde 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/log_entry_timestamp_column.tsx @@ -19,7 +19,7 @@ interface LogEntryTimestampColumnProps { export const LogEntryTimestampColumn = memo( ({ isHighlighted, isHovered, time }) => { - const formattedTime = useFormattedTime(time); + const formattedTime = useFormattedTime(time, { format: 'time' }); return ( @@ -45,11 +45,8 @@ const TimestampColumnContent = LogEntryColumnContent.extend.attrs<{ isHovered: boolean; isHighlighted: boolean; }>({})` - background-color: ${props => props.theme.eui.euiColorLightestShade}; - border-right: solid 2px ${props => props.theme.eui.euiColorLightShade}; color: ${props => props.theme.eui.euiColorDarkShade}; overflow: hidden; - text-align: right; text-overflow: clip; white-space: pre; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 3c1fb2ceec7f6..d439308194d18 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -6,7 +6,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; +import React, { Fragment, useMemo } from 'react'; +import moment from 'moment'; import euiStyled from '../../../../../../common/eui_styled_components'; import { TextScale } from '../../../../common/log_text_scale'; @@ -26,6 +27,7 @@ import { MeasurableItemView } from './measurable_item_view'; import { VerticalScrollPanel } from './vertical_scroll_panel'; import { getColumnWidths, LogEntryColumnWidths } from './log_entry_column'; import { useMeasuredCharacterDimensions } from './text_styles'; +import { LogDateRow } from './log_date_row'; interface ScrollableLogTextStreamViewProps { columnConfigurations: LogColumnConfiguration[]; @@ -188,35 +190,47 @@ export class ScrollableLogTextStreamView extends React.PureComponent< isStreaming={false} lastStreamingUpdate={null} /> - {items.map(item => ( - - {itemMeasureRef => ( - - )} - - ))} + {items.map((item, idx) => { + const currentTimestamp = item.logEntry.key.time; + let showDate = false; + + if (idx > 0) { + const prevTimestamp = items[idx - 1].logEntry.key.time; + showDate = !moment(currentTimestamp).isSame(prevTimestamp, 'day'); + } + + return ( + + {showDate && } + + {itemMeasureRef => ( + + )} + + + ); + })} = ({ children, columnConfigurations, scale }) => { const { CharacterDimensionsProbe, dimensions } = useMeasuredCharacterDimensions(scale); const referenceTime = useMemo(() => Date.now(), []); - const formattedCurrentDate = useFormattedTime(referenceTime); + const formattedCurrentDate = useFormattedTime(referenceTime, { format: 'time' }); const columnWidths = useMemo( () => getColumnWidths(columnConfigurations, dimensions.width, formattedCurrentDate.length), [columnConfigurations, dimensions.width, formattedCurrentDate] diff --git a/x-pack/legacy/plugins/infra/public/utils/formatters/datetime.ts b/x-pack/legacy/plugins/infra/public/utils/formatters/datetime.ts new file mode 100644 index 0000000000000..732bc7fe74bf9 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/utils/formatters/datetime.ts @@ -0,0 +1,17 @@ +/* + * 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 function localizedDate(dateTime: number | Date, locale: string = i18n.getLocale()) { + const formatter = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + + return formatter.format(dateTime); +} diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index 3447e03a680e1..183acf3a980ee 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -66,7 +66,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream'); const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels(); - expect(columnHeaderLabels).to.eql(['Timestamp', 'event.dataset', 'Message']); + expect(columnHeaderLabels).to.eql(['Oct 17, 2018', 'event.dataset', 'Message']); const logStreamEntries = await infraLogStream.getStreamEntries(); expect(logStreamEntries.length).to.be.greaterThan(0); @@ -98,7 +98,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // TODO: make test more robust // expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp']); - expect(columnHeaderLabels).to.eql(['Timestamp', 'host.name']); + expect(columnHeaderLabels).to.eql(['Oct 17, 2018', 'host.name']); const logStreamEntries = await infraLogStream.getStreamEntries();