[Logs UI] Custom rendering for <LogStream />
columns (#85148)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
404d846f09
commit
4778365fc8
|
@ -98,6 +98,74 @@ There are three column types:
|
|||
<td>A specific field specified in the `field` property.
|
||||
</table>
|
||||
|
||||
### Custom column rendering
|
||||
|
||||
Besides customizing what columns are shown, you can also customize how a column is rendered. You can customize the width of the column, the text of the header, and how each value is rendered within the cell
|
||||
|
||||
#### `width` option
|
||||
|
||||
The `width` modifies the width of the column. It can be a number (in pixels) or a string with a valid CSS value.
|
||||
|
||||
```tsx
|
||||
<LogStream
|
||||
startTimestamp={...}
|
||||
endTimetsamp={...}
|
||||
columns={[
|
||||
{ type: 'timestamp', width: 100 }, // Same as "100px"
|
||||
{ type: 'field', field: 'event.dataset', width: "50%" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
#### `header` option
|
||||
|
||||
The `header` takes either a `boolean` value that specifies if the header should be rendered or not, or a `string` with the text to render.
|
||||
|
||||
```tsx
|
||||
<LogStream
|
||||
startTimestamp={...}
|
||||
endTimetsamp={...}
|
||||
columns={[
|
||||
// Don't show anything in the header
|
||||
{ type: 'timestamp', header: false },
|
||||
// Show a custom string in the header
|
||||
{ type: 'field', field: 'event.dataset', header: "Dataset of the event" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
The default is `true`, which render the default values for each column type:
|
||||
|
||||
| Column type | Default value |
|
||||
| ----------- | ------------------------------------- |
|
||||
| `timestamp` | Date of the top-most visible log line |
|
||||
| `message` | `"Message"` literal |
|
||||
| `field` | Field name |
|
||||
|
||||
#### `render` option
|
||||
|
||||
The `render` takes a function to customize the rendering of the column. The first argument is the value of the column. The function must return a valid `ReactNode`.
|
||||
|
||||
```tsx
|
||||
<LogStream
|
||||
startTimestamp={...}
|
||||
endTimetsamp={...}
|
||||
columns={[
|
||||
{ type: 'timestamp', render: (timestamp) => <b>{new Date(timestamp).toString()}</b>; },
|
||||
{ type: 'field', field: 'log.level', render: (value) => value === 'warn' ? '⚠️' : 'ℹ️' }
|
||||
{ type: 'message', render: (message) => message.toUpperCase() }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
The first argument's type depends on the column type.
|
||||
|
||||
| Column type | Type of the `value` |
|
||||
| ----------- | ---------------------------------------------------------------------- |
|
||||
| `timestamp` | `number`. The epoch_millis of the log line |
|
||||
| `message` | `string`. The processed log message |
|
||||
| `field` | `JsonValue`. The type of the field itself. Must be checked at runtime. |
|
||||
|
||||
### Considerations
|
||||
|
||||
As mentioned in the prerequisites, the component relies on `kibana-react` to access kibana's core services. If this is not the case the component will throw an exception when rendering. We advise to use an `<EuiErrorBoundary>` in your component hierarchy to catch this error if necessary.
|
||||
|
|
|
@ -11,17 +11,45 @@ import { euiStyled } from '../../../../observability/public';
|
|||
import { LogEntryCursor } from '../../../common/log_entry';
|
||||
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { LogSourceConfigurationProperties, useLogSource } from '../../containers/logs/log_source';
|
||||
import { useLogSource } from '../../containers/logs/log_source';
|
||||
import { useLogStream } from '../../containers/logs/log_stream';
|
||||
|
||||
import { ScrollableLogTextStreamView } from '../logging/log_text_stream';
|
||||
import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration';
|
||||
import { JsonValue } from '../../../common/typed_json';
|
||||
|
||||
const PAGE_THRESHOLD = 2;
|
||||
|
||||
interface CommonColumnDefinition {
|
||||
/** width of the column, in CSS units */
|
||||
width?: number | string;
|
||||
/** Content for the header. `true` renders the field name. `false` renders nothing. A string renders a custom value */
|
||||
header?: boolean | string;
|
||||
}
|
||||
|
||||
interface TimestampColumnDefinition extends CommonColumnDefinition {
|
||||
type: 'timestamp';
|
||||
/** Timestamp renderer. Takes a epoch_millis and returns a valid `ReactNode` */
|
||||
render?: (timestamp: number) => React.ReactNode;
|
||||
}
|
||||
|
||||
interface MessageColumnDefinition extends CommonColumnDefinition {
|
||||
type: 'message';
|
||||
/** Message renderer. Takes the processed message and returns a valid `ReactNode` */
|
||||
render?: (message: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
interface FieldColumnDefinition extends CommonColumnDefinition {
|
||||
type: 'field';
|
||||
field: string;
|
||||
/** Field renderer. Takes the value of the field and returns a valid `ReactNode` */
|
||||
render?: (value: JsonValue) => React.ReactNode;
|
||||
}
|
||||
|
||||
type LogColumnDefinition =
|
||||
| { type: 'timestamp' }
|
||||
| { type: 'message' }
|
||||
| { type: 'field'; field: string };
|
||||
| TimestampColumnDefinition
|
||||
| MessageColumnDefinition
|
||||
| FieldColumnDefinition;
|
||||
|
||||
export interface LogStreamProps {
|
||||
sourceId?: string;
|
||||
|
@ -178,15 +206,24 @@ const LogStreamContent = euiStyled.div<{ height: string }>`
|
|||
|
||||
function convertLogColumnDefinitionToLogSourceColumnDefinition(
|
||||
columns: LogColumnDefinition[]
|
||||
): LogSourceConfigurationProperties['logColumns'] {
|
||||
): LogColumnRenderConfiguration[] {
|
||||
return columns.map((column) => {
|
||||
// We extract the { width, header, render } inside each block so the TS compiler uses the right type for `render`
|
||||
switch (column.type) {
|
||||
case 'timestamp':
|
||||
return { timestampColumn: { id: '___#timestamp' } };
|
||||
case 'message':
|
||||
return { messageColumn: { id: '___#message' } };
|
||||
case 'field':
|
||||
return { fieldColumn: { id: `___#${column.field}`, field: column.field } };
|
||||
case 'timestamp': {
|
||||
const { width, header, render } = column;
|
||||
return { timestampColumn: { id: '___#timestamp', width, header, render } };
|
||||
}
|
||||
case 'message': {
|
||||
const { width, header, render } = column;
|
||||
return { messageColumn: { id: '___#message', width, header, render } };
|
||||
}
|
||||
case 'field': {
|
||||
const { width, header, render } = column;
|
||||
return {
|
||||
fieldColumn: { id: `___#${column.field}`, field: column.field, width, header, render },
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,16 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext } from 'react';
|
||||
import { transparentize } from 'polished';
|
||||
|
||||
import { euiStyled } from '../../../../../observability/public';
|
||||
import {
|
||||
LogColumnConfiguration,
|
||||
isTimestampLogColumnConfiguration,
|
||||
isFieldLogColumnConfiguration,
|
||||
isMessageLogColumnConfiguration,
|
||||
} from '../../../utils/source_configuration';
|
||||
import {
|
||||
LogEntryColumn,
|
||||
LogEntryColumnContent,
|
||||
|
@ -23,43 +18,82 @@ import {
|
|||
import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel';
|
||||
import { LogPositionState } from '../../../containers/logs/log_position';
|
||||
import { localizedDate } from '../../../../common/formatters/datetime';
|
||||
import {
|
||||
LogColumnRenderConfiguration,
|
||||
isTimestampColumnRenderConfiguration,
|
||||
isMessageColumnRenderConfiguration,
|
||||
isFieldColumnRenderConfiguration,
|
||||
} from '../../../utils/log_column_render_configuration';
|
||||
|
||||
export const LogColumnHeaders: React.FunctionComponent<{
|
||||
columnConfigurations: LogColumnConfiguration[];
|
||||
columnConfigurations: LogColumnRenderConfiguration[];
|
||||
columnWidths: LogEntryColumnWidths;
|
||||
}> = ({ columnConfigurations, columnWidths }) => {
|
||||
const { firstVisiblePosition } = useContext(LogPositionState.Context);
|
||||
return (
|
||||
<LogColumnHeadersWrapper>
|
||||
{columnConfigurations.map((columnConfiguration) => {
|
||||
if (isTimestampLogColumnConfiguration(columnConfiguration)) {
|
||||
if (isTimestampColumnRenderConfiguration(columnConfiguration)) {
|
||||
let columnHeader;
|
||||
if (columnConfiguration.timestampColumn.header === false) {
|
||||
columnHeader = null;
|
||||
} else if (typeof columnConfiguration.timestampColumn.header === 'string') {
|
||||
columnHeader = columnConfiguration.timestampColumn.header;
|
||||
} else {
|
||||
columnHeader = firstVisiblePosition
|
||||
? localizedDate(firstVisiblePosition.time)
|
||||
: i18n.translate('xpack.infra.logs.stream.timestampColumnTitle', {
|
||||
defaultMessage: 'Timestamp',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<LogColumnHeader
|
||||
key={columnConfiguration.timestampColumn.id}
|
||||
columnWidth={columnWidths[columnConfiguration.timestampColumn.id]}
|
||||
data-test-subj="logColumnHeader timestampLogColumnHeader"
|
||||
>
|
||||
{firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'}
|
||||
{columnHeader}
|
||||
</LogColumnHeader>
|
||||
);
|
||||
} else if (isMessageLogColumnConfiguration(columnConfiguration)) {
|
||||
} else if (isMessageColumnRenderConfiguration(columnConfiguration)) {
|
||||
let columnHeader;
|
||||
if (columnConfiguration.messageColumn.header === false) {
|
||||
columnHeader = null;
|
||||
} else if (typeof columnConfiguration.messageColumn.header === 'string') {
|
||||
columnHeader = columnConfiguration.messageColumn.header;
|
||||
} else {
|
||||
columnHeader = i18n.translate('xpack.infra.logs.stream.messageColumnTitle', {
|
||||
defaultMessage: 'Message',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<LogColumnHeader
|
||||
columnWidth={columnWidths[columnConfiguration.messageColumn.id]}
|
||||
data-test-subj="logColumnHeader messageLogColumnHeader"
|
||||
key={columnConfiguration.messageColumn.id}
|
||||
>
|
||||
Message
|
||||
{columnHeader}
|
||||
</LogColumnHeader>
|
||||
);
|
||||
} else if (isFieldLogColumnConfiguration(columnConfiguration)) {
|
||||
} else if (isFieldColumnRenderConfiguration(columnConfiguration)) {
|
||||
let columnHeader;
|
||||
if (columnConfiguration.fieldColumn.header === false) {
|
||||
columnHeader = null;
|
||||
} else if (typeof columnConfiguration.fieldColumn.header === 'string') {
|
||||
columnHeader = columnConfiguration.fieldColumn.header;
|
||||
} else {
|
||||
columnHeader = columnConfiguration.fieldColumn.field;
|
||||
}
|
||||
|
||||
return (
|
||||
<LogColumnHeader
|
||||
columnWidth={columnWidths[columnConfiguration.fieldColumn.id]}
|
||||
data-test-subj="logColumnHeader fieldLogColumnHeader"
|
||||
key={columnConfiguration.fieldColumn.id}
|
||||
>
|
||||
{columnConfiguration.fieldColumn.field}
|
||||
{columnHeader}
|
||||
</LogColumnHeader>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,12 @@ export const FieldValue: React.FC<{
|
|||
highlightTerms: string[];
|
||||
isActiveHighlight: boolean;
|
||||
value: JsonArray;
|
||||
}> = React.memo(({ highlightTerms, isActiveHighlight, value }) => {
|
||||
render?: (value: JsonValue) => React.ReactNode;
|
||||
}> = React.memo(({ highlightTerms, isActiveHighlight, value, render }) => {
|
||||
if (render) {
|
||||
return <>{render(value.length === 1 ? value[0] : value)}</>;
|
||||
}
|
||||
|
||||
if (value.length === 1) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -9,10 +9,11 @@ import { useMemo } from 'react';
|
|||
import { euiStyled } from '../../../../../observability/public';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import {
|
||||
isMessageLogColumnConfiguration,
|
||||
isTimestampLogColumnConfiguration,
|
||||
LogColumnConfiguration,
|
||||
} from '../../../utils/source_configuration';
|
||||
LogColumnRenderConfiguration,
|
||||
isTimestampColumnRenderConfiguration,
|
||||
isMessageColumnRenderConfiguration,
|
||||
columnWidthToCSS,
|
||||
} from '../../../utils/log_column_render_configuration';
|
||||
import { useFormattedTime, TimeFormat } from '../../formatted_time';
|
||||
import { useMeasuredCharacterDimensions } from './text_styles';
|
||||
|
||||
|
@ -59,42 +60,58 @@ export interface LogEntryColumnWidths {
|
|||
}
|
||||
|
||||
export const getColumnWidths = (
|
||||
columns: LogColumnConfiguration[],
|
||||
columns: LogColumnRenderConfiguration[],
|
||||
characterWidth: number,
|
||||
formattedDateWidth: number
|
||||
): LogEntryColumnWidths =>
|
||||
columns.reduce<LogEntryColumnWidths>(
|
||||
(columnWidths, column) => {
|
||||
if (isTimestampLogColumnConfiguration(column)) {
|
||||
if (isTimestampColumnRenderConfiguration(column)) {
|
||||
const customWidth = column.timestampColumn.width
|
||||
? columnWidthToCSS(column.timestampColumn.width)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...columnWidths,
|
||||
[column.timestampColumn.id]: {
|
||||
growWeight: 0,
|
||||
shrinkWeight: 0,
|
||||
baseWidth: `${
|
||||
Math.ceil(characterWidth * formattedDateWidth * DATE_COLUMN_SLACK_FACTOR) +
|
||||
2 * COLUMN_PADDING
|
||||
}px`,
|
||||
baseWidth:
|
||||
customWidth ??
|
||||
`${
|
||||
Math.ceil(characterWidth * formattedDateWidth * DATE_COLUMN_SLACK_FACTOR) +
|
||||
2 * COLUMN_PADDING
|
||||
}px`,
|
||||
},
|
||||
};
|
||||
} else if (isMessageLogColumnConfiguration(column)) {
|
||||
} else if (isMessageColumnRenderConfiguration(column)) {
|
||||
const customWidth = column.messageColumn.width
|
||||
? columnWidthToCSS(column.messageColumn.width)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...columnWidths,
|
||||
[column.messageColumn.id]: {
|
||||
growWeight: 5,
|
||||
shrinkWeight: 0,
|
||||
baseWidth: '0%',
|
||||
baseWidth: customWidth ?? '0%',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const customWidth = column.fieldColumn.width
|
||||
? columnWidthToCSS(column.fieldColumn.width)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...columnWidths,
|
||||
[column.fieldColumn.id]: {
|
||||
growWeight: 1,
|
||||
growWeight: customWidth ? 0 : 1,
|
||||
shrinkWeight: 0,
|
||||
baseWidth: `${
|
||||
Math.ceil(characterWidth * FIELD_COLUMN_MIN_WIDTH_CHARACTERS) + 2 * COLUMN_PADDING
|
||||
}px`,
|
||||
baseWidth:
|
||||
customWidth ??
|
||||
`${
|
||||
Math.ceil(characterWidth * FIELD_COLUMN_MIN_WIDTH_CHARACTERS) + 2 * COLUMN_PADDING
|
||||
}px`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -119,7 +136,7 @@ export const useColumnWidths = ({
|
|||
scale,
|
||||
timeFormat = 'time',
|
||||
}: {
|
||||
columnConfigurations: LogColumnConfiguration[];
|
||||
columnConfigurations: LogColumnRenderConfiguration[];
|
||||
scale: TextScale;
|
||||
timeFormat?: TimeFormat;
|
||||
}) => {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { JsonValue } from '../../../../common/typed_json';
|
||||
import { euiStyled } from '../../../../../observability/public';
|
||||
import { LogColumn } from '../../../../common/http_api';
|
||||
import { isFieldColumn, isHighlightFieldColumn } from '../../../utils/log_entry';
|
||||
|
@ -22,6 +23,7 @@ interface LogEntryFieldColumnProps {
|
|||
highlights: LogColumn[];
|
||||
isActiveHighlight: boolean;
|
||||
wrapMode: WrapMode;
|
||||
render?: (value: JsonValue) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const LogEntryFieldColumn: React.FunctionComponent<LogEntryFieldColumnProps> = ({
|
||||
|
@ -29,6 +31,7 @@ export const LogEntryFieldColumn: React.FunctionComponent<LogEntryFieldColumnPro
|
|||
highlights: [firstHighlight], // we only support one highlight for now
|
||||
isActiveHighlight,
|
||||
wrapMode,
|
||||
render,
|
||||
}) => {
|
||||
if (isFieldColumn(columnValue)) {
|
||||
return (
|
||||
|
@ -37,6 +40,7 @@ export const LogEntryFieldColumn: React.FunctionComponent<LogEntryFieldColumnPro
|
|||
highlightTerms={isHighlightFieldColumn(firstHighlight) ? firstHighlight.highlights : []}
|
||||
isActiveHighlight={isActiveHighlight}
|
||||
value={columnValue.value}
|
||||
render={render}
|
||||
/>
|
||||
</FieldColumnContent>
|
||||
);
|
||||
|
|
|
@ -28,10 +28,11 @@ interface LogEntryMessageColumnProps {
|
|||
highlights: LogColumn[];
|
||||
isActiveHighlight: boolean;
|
||||
wrapMode: WrapMode;
|
||||
render?: (message: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const LogEntryMessageColumn = memo<LogEntryMessageColumnProps>(
|
||||
({ columnValue, highlights, isActiveHighlight, wrapMode }) => {
|
||||
({ columnValue, highlights, isActiveHighlight, wrapMode, render }) => {
|
||||
const message = useMemo(
|
||||
() =>
|
||||
isMessageColumn(columnValue)
|
||||
|
@ -40,7 +41,16 @@ export const LogEntryMessageColumn = memo<LogEntryMessageColumnProps>(
|
|||
[columnValue, highlights, isActiveHighlight]
|
||||
);
|
||||
|
||||
return <MessageColumnContent wrapMode={wrapMode}>{message}</MessageColumnContent>;
|
||||
const messageAsString = useMemo(
|
||||
() => (isMessageColumn(columnValue) ? renderMessageSegments(columnValue.message) : ''),
|
||||
[columnValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<MessageColumnContent wrapMode={wrapMode}>
|
||||
{render ? render(messageAsString) : message}
|
||||
</MessageColumnContent>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -90,3 +100,16 @@ const formatMessageSegments = (
|
|||
|
||||
return 'failed to format message';
|
||||
});
|
||||
|
||||
const renderMessageSegments = (messageSegments: LogMessagePart[]): string => {
|
||||
return messageSegments
|
||||
.map((messageSegment) => {
|
||||
if (isConstantSegment(messageSegment)) {
|
||||
return messageSegment.constant;
|
||||
}
|
||||
if (isFieldSegment(messageSegment)) {
|
||||
return messageSegment.value.toString();
|
||||
}
|
||||
})
|
||||
.join(' ');
|
||||
};
|
||||
|
|
|
@ -10,12 +10,6 @@ import { isEmpty } from 'lodash';
|
|||
|
||||
import { euiStyled, useUiTracker } from '../../../../../observability/public';
|
||||
import { isTimestampColumn } from '../../../utils/log_entry';
|
||||
import {
|
||||
LogColumnConfiguration,
|
||||
isTimestampLogColumnConfiguration,
|
||||
isMessageLogColumnConfiguration,
|
||||
isFieldLogColumnConfiguration,
|
||||
} from '../../../utils/source_configuration';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { LogEntryColumn, LogEntryColumnWidths, iconColumnId } from './log_entry_column';
|
||||
import { LogEntryFieldColumn } from './log_entry_field_column';
|
||||
|
@ -24,6 +18,12 @@ import { LogEntryTimestampColumn } from './log_entry_timestamp_column';
|
|||
import { monospaceTextStyle, hoveredContentStyle, highlightedContentStyle } from './text_styles';
|
||||
import { LogEntry, LogColumn } from '../../../../common/http_api';
|
||||
import { LogEntryContextMenu } from './log_entry_context_menu';
|
||||
import {
|
||||
LogColumnRenderConfiguration,
|
||||
isTimestampColumnRenderConfiguration,
|
||||
isMessageColumnRenderConfiguration,
|
||||
isFieldColumnRenderConfiguration,
|
||||
} from '../../../utils/log_column_render_configuration';
|
||||
|
||||
const MENU_LABEL = i18n.translate('xpack.infra.logEntryItemView.logEntryActionsMenuToolTip', {
|
||||
defaultMessage: 'View actions for line',
|
||||
|
@ -42,7 +42,7 @@ const LOG_VIEW_IN_CONTEXT_LABEL = i18n.translate(
|
|||
|
||||
interface LogEntryRowProps {
|
||||
boundingBoxRef?: React.Ref<Element>;
|
||||
columnConfigurations: LogColumnConfiguration[];
|
||||
columnConfigurations: LogColumnRenderConfiguration[];
|
||||
columnWidths: LogEntryColumnWidths;
|
||||
highlights: LogEntry[];
|
||||
isActiveHighlight: boolean;
|
||||
|
@ -162,7 +162,7 @@ export const LogEntryRow = memo(
|
|||
scale={scale}
|
||||
>
|
||||
{columnConfigurations.map((columnConfiguration) => {
|
||||
if (isTimestampLogColumnConfiguration(columnConfiguration)) {
|
||||
if (isTimestampColumnRenderConfiguration(columnConfiguration)) {
|
||||
const column = logEntryColumnsById[columnConfiguration.timestampColumn.id];
|
||||
const columnWidth = columnWidths[columnConfiguration.timestampColumn.id];
|
||||
|
||||
|
@ -173,11 +173,14 @@ export const LogEntryRow = memo(
|
|||
{...columnWidth}
|
||||
>
|
||||
{isTimestampColumn(column) ? (
|
||||
<LogEntryTimestampColumn time={column.timestamp} />
|
||||
<LogEntryTimestampColumn
|
||||
time={column.timestamp}
|
||||
render={columnConfiguration.timestampColumn.render}
|
||||
/>
|
||||
) : null}
|
||||
</LogEntryColumn>
|
||||
);
|
||||
} else if (isMessageLogColumnConfiguration(columnConfiguration)) {
|
||||
} else if (isMessageColumnRenderConfiguration(columnConfiguration)) {
|
||||
const column = logEntryColumnsById[columnConfiguration.messageColumn.id];
|
||||
const columnWidth = columnWidths[columnConfiguration.messageColumn.id];
|
||||
|
||||
|
@ -193,11 +196,12 @@ export const LogEntryRow = memo(
|
|||
highlights={highlightsByColumnId[column.columnId] || []}
|
||||
isActiveHighlight={isActiveHighlight}
|
||||
wrapMode={wrap ? 'long' : 'pre-wrapped'}
|
||||
render={columnConfiguration.messageColumn.render}
|
||||
/>
|
||||
) : null}
|
||||
</LogEntryColumn>
|
||||
);
|
||||
} else if (isFieldLogColumnConfiguration(columnConfiguration)) {
|
||||
} else if (isFieldColumnRenderConfiguration(columnConfiguration)) {
|
||||
const column = logEntryColumnsById[columnConfiguration.fieldColumn.id];
|
||||
const columnWidth = columnWidths[columnConfiguration.fieldColumn.id];
|
||||
|
||||
|
@ -213,6 +217,7 @@ export const LogEntryRow = memo(
|
|||
highlights={highlightsByColumnId[column.columnId] || []}
|
||||
isActiveHighlight={isActiveHighlight}
|
||||
wrapMode={wrap ? 'long' : 'pre-wrapped'}
|
||||
render={columnConfiguration.fieldColumn.render}
|
||||
/>
|
||||
) : null}
|
||||
</LogEntryColumn>
|
||||
|
|
|
@ -13,13 +13,14 @@ import { LogEntryColumnContent } from './log_entry_column';
|
|||
interface LogEntryTimestampColumnProps {
|
||||
format?: TimeFormat;
|
||||
time: number;
|
||||
render?: (timestamp: number) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const LogEntryTimestampColumn = memo<LogEntryTimestampColumnProps>(
|
||||
({ format = 'time', time }) => {
|
||||
({ format = 'time', time, render }) => {
|
||||
const formattedTime = useFormattedTime(time, { format });
|
||||
|
||||
return <TimestampColumnContent>{formattedTime}</TimestampColumnContent>;
|
||||
return <TimestampColumnContent>{render ? render(time) : formattedTime}</TimestampColumnContent>;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import { euiStyled } from '../../../../../observability/public';
|
|||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { TimeKey, UniqueTimeKey } from '../../../../common/time';
|
||||
import { callWithoutRepeats } from '../../../utils/handlers';
|
||||
import { LogColumnConfiguration } from '../../../utils/source_configuration';
|
||||
import { AutoSizer } from '../../auto_sizer';
|
||||
import { NoData } from '../../empty_states';
|
||||
import { InfraLoadingPanel } from '../../loading';
|
||||
|
@ -27,9 +26,10 @@ import { VerticalScrollPanel } from './vertical_scroll_panel';
|
|||
import { useColumnWidths, LogEntryColumnWidths } from './log_entry_column';
|
||||
import { LogDateRow } from './log_date_row';
|
||||
import { LogEntry } from '../../../../common/http_api';
|
||||
import { LogColumnRenderConfiguration } from '../../../utils/log_column_render_configuration';
|
||||
|
||||
interface ScrollableLogTextStreamViewProps {
|
||||
columnConfigurations: LogColumnConfiguration[];
|
||||
columnConfigurations: LogColumnRenderConfiguration[];
|
||||
items: StreamItem[];
|
||||
scale: TextScale;
|
||||
wrap: boolean;
|
||||
|
@ -379,7 +379,7 @@ const WithColumnWidths: React.FunctionComponent<{
|
|||
columnWidths: LogEntryColumnWidths;
|
||||
CharacterDimensionsProbe: React.ComponentType;
|
||||
}) => React.ReactElement<any> | null;
|
||||
columnConfigurations: LogColumnConfiguration[];
|
||||
columnConfigurations: LogColumnRenderConfiguration[];
|
||||
scale: TextScale;
|
||||
}> = ({ children, columnConfigurations, scale }) => {
|
||||
const childParams = useColumnWidths({ columnConfigurations, scale });
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { ReactNode } from 'react';
|
||||
import { JsonValue } from '../../common/typed_json';
|
||||
|
||||
/**
|
||||
* Interface for common configuration properties, regardless of the column type.
|
||||
*/
|
||||
interface CommonRenderConfiguration {
|
||||
id: string;
|
||||
width?: number | string;
|
||||
header?: boolean | string;
|
||||
}
|
||||
|
||||
interface TimestampColumnRenderConfiguration {
|
||||
timestampColumn: CommonRenderConfiguration & {
|
||||
render?: (timestamp: number) => ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
interface MessageColumnRenderConfiguration {
|
||||
messageColumn: CommonRenderConfiguration & {
|
||||
render?: (message: string) => ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
interface FieldColumnRenderConfiguration {
|
||||
fieldColumn: CommonRenderConfiguration & {
|
||||
field: string;
|
||||
render?: (value: JsonValue) => ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export type LogColumnRenderConfiguration =
|
||||
| TimestampColumnRenderConfiguration
|
||||
| MessageColumnRenderConfiguration
|
||||
| FieldColumnRenderConfiguration;
|
||||
|
||||
export function isTimestampColumnRenderConfiguration(
|
||||
column: LogColumnRenderConfiguration
|
||||
): column is TimestampColumnRenderConfiguration {
|
||||
return 'timestampColumn' in column;
|
||||
}
|
||||
|
||||
export function isMessageColumnRenderConfiguration(
|
||||
column: LogColumnRenderConfiguration
|
||||
): column is MessageColumnRenderConfiguration {
|
||||
return 'messageColumn' in column;
|
||||
}
|
||||
|
||||
export function isFieldColumnRenderConfiguration(
|
||||
column: LogColumnRenderConfiguration
|
||||
): column is FieldColumnRenderConfiguration {
|
||||
return 'fieldColumn' in column;
|
||||
}
|
||||
|
||||
export function columnWidthToCSS(width: number | string) {
|
||||
return typeof width === 'number' ? `${width}px` : width;
|
||||
}
|
Loading…
Reference in a new issue