Skip to content

Commit

Permalink
[Logs UI] Transmit and render array field values in log entries (#81385)
Browse files Browse the repository at this point in the history
Co-authored-by: Alejandro Fernández Gómez <antarticonorte@gmail.com>
  • Loading branch information
2 people authored and Alejandro Fernández Gómez committed Oct 28, 2020
1 parent c5d28ef commit 0bbb6d8
Show file tree
Hide file tree
Showing 40 changed files with 3,963 additions and 2,965 deletions.
5 changes: 3 additions & 2 deletions x-pack/plugins/infra/common/http_api/log_entries/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import * as rt from 'io-ts';
import { jsonArrayRT } from '../../typed_json';
import { logEntriesCursorRT } from './common';

export const LOG_ENTRIES_PATH = '/api/log_entries/entries';
Expand Down Expand Up @@ -54,7 +55,7 @@ export const logMessageConstantPartRT = rt.type({
});
export const logMessageFieldPartRT = rt.type({
field: rt.string,
value: rt.unknown,
value: jsonArrayRT,
highlights: rt.array(rt.string),
});

Expand All @@ -64,7 +65,7 @@ export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt
export const logFieldColumnRT = rt.type({
columnId: rt.string,
field: rt.string,
value: rt.unknown,
value: jsonArrayRT,
highlights: rt.array(rt.string),
});
export const logMessageColumnRT = rt.type({
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/infra/common/http_api/log_entries/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const logEntriesItemRequestRT = rt.type({

export type LogEntriesItemRequest = rt.TypeOf<typeof logEntriesItemRequestRT>;

const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.string });
const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.array(rt.string) });
const logEntriesItemRT = rt.type({
id: rt.string,
index: rt.string,
Expand Down
22 changes: 16 additions & 6 deletions x-pack/plugins/infra/common/typed_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/

export type JsonValue = null | boolean | number | string | JsonObject | JsonArray;
import * as rt from 'io-ts';
import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana_utils/common';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface JsonArray extends Array<JsonValue> {}
export const jsonScalarRT = rt.union([rt.null, rt.boolean, rt.number, rt.string]);

export interface JsonObject {
[key: string]: JsonValue;
}
export const jsonValueRT: rt.Type<JsonValue> = rt.recursion('JsonValue', () =>
rt.union([jsonScalarRT, jsonArrayRT, jsonObjectRT])
);

export const jsonArrayRT: rt.Type<JsonArray> = rt.recursion('JsonArray', () =>
rt.array(jsonValueRT)
);

export const jsonObjectRT: rt.Type<JsonObject> = rt.recursion('JsonObject', () =>
rt.record(rt.string, jsonValueRT)
);

export { JsonValue, JsonArray, JsonObject };
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'host.ip', value: 'HOST_IP' }],
fields: [{ field: 'host.ip', value: ['HOST_IP'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
Expand Down Expand Up @@ -59,7 +59,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'container.id', value: 'CONTAINER_ID' }],
fields: [{ field: 'container.id', value: ['CONTAINER_ID'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
Expand Down Expand Up @@ -89,7 +89,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'kubernetes.pod.uid', value: 'POD_UID' }],
fields: [{ field: 'kubernetes.pod.uid', value: ['POD_UID'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
Expand Down Expand Up @@ -120,9 +120,9 @@ describe('LogEntryActionsMenu component', () => {
<LogEntryActionsMenu
logItem={{
fields: [
{ field: 'container.id', value: 'CONTAINER_ID' },
{ field: 'host.ip', value: 'HOST_IP' },
{ field: 'kubernetes.pod.uid', value: 'POD_UID' },
{ field: 'container.id', value: ['CONTAINER_ID'] },
{ field: 'host.ip', value: ['HOST_IP'] },
{ field: 'kubernetes.pod.uid', value: ['POD_UID'] },
],
id: 'ITEM_ID',
index: 'INDEX',
Expand Down Expand Up @@ -189,7 +189,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'trace.id', value: '1234567' }],
fields: [{ field: 'trace.id', value: ['1234567'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
Expand Down Expand Up @@ -221,8 +221,8 @@ describe('LogEntryActionsMenu component', () => {
<LogEntryActionsMenu
logItem={{
fields: [
{ field: 'trace.id', value: '1234567' },
{ field: '@timestamp', value: timestamp },
{ field: 'trace.id', value: ['1234567'] },
{ field: '@timestamp', value: [timestamp] },
],
id: 'ITEM_ID',
index: 'INDEX',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as rt from 'io-ts';
import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
import { useVisibilityState } from '../../../utils/use_visibility_state';
import { getTraceUrl } from '../../../../../apm/public';
import { LogEntriesItem } from '../../../../common/http_api';
import { useLinkProps, LinkDescriptor } from '../../../hooks/use_link_props';
import { decodeOrThrow } from '../../../../common/runtime_types';

const UPTIME_FIELDS = ['container.id', 'host.ip', 'kubernetes.pod.uid'];

Expand Down Expand Up @@ -97,12 +95,7 @@ const getUptimeLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
.filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field))
.reduce<string[]>((acc, fieldItem) => {
const { field, value } = fieldItem;
try {
const parsedValue = decodeOrThrow(rt.array(rt.string))(JSON.parse(value));
return acc.concat(parsedValue.map((val) => `${field}:${val}`));
} catch (e) {
return acc.concat([`${field}:${value}`]);
}
return acc.concat(value.map((val) => `${field}:${val}`));
}, []);

if (searchExpressions.length === 0) {
Expand All @@ -119,15 +112,15 @@ const getUptimeLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {

const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
const traceIdEntry = logItem.fields.find(
({ field, value }) => value != null && field === 'trace.id'
({ field, value }) => value[0] != null && field === 'trace.id'
);

if (!traceIdEntry) {
return undefined;
}

const timestampField = logItem.fields.find(({ field }) => field === '@timestamp');
const timestamp = timestampField ? timestampField.value : null;
const timestamp = timestampField ? timestampField.value[0] : null;
const { rangeFrom, rangeTo } = timestamp
? (() => {
const from = new Date(timestamp);
Expand All @@ -142,6 +135,6 @@ const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {

return {
app: 'apm',
hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }),
hash: getTraceUrl({ traceId: traceIdEntry.value[0], rangeFrom, rangeTo }),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const LogEntryFlyout = ({
onClick={createFilterHandler(item)}
/>
</EuiToolTip>
{item.value}
{formatValue(item.value)}
</span>
),
},
Expand Down Expand Up @@ -147,3 +147,7 @@ export const InfraFlyoutLoadingPanel = euiStyled.div`
bottom: 0;
left: 0;
`;

function formatValue(value: string[]) {
return value.length > 1 ? value.join(', ') : value[0];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 stringify from 'json-stable-stringify';
import React from 'react';
import { euiStyled } from '../../../../../observability/public';
import { JsonArray, JsonValue } from '../../../../common/typed_json';
import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting';

export const FieldValue: React.FC<{
highlightTerms: string[];
isActiveHighlight: boolean;
value: JsonArray;
}> = React.memo(({ highlightTerms, isActiveHighlight, value }) => {
if (value.length === 1) {
return (
<>
{highlightFieldValue(
formatValue(value[0]),
highlightTerms,
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
)}
</>
);
} else if (value.length > 1) {
return (
<ul data-test-subj="LogEntryFieldValues">
{value.map((entry, i) => (
<CommaSeparatedLi
key={`LogEntryFieldValue-${i}`}
data-test-subj={`LogEntryFieldValue-${i}`}
>
{highlightFieldValue(
formatValue(entry),
highlightTerms,
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
)}
</CommaSeparatedLi>
))}
</ul>
);
}

return null;
});

const formatValue = (value: JsonValue): string => {
if (typeof value === 'string') {
return value;
}

return stringify(value);
};

const CommaSeparatedLi = euiStyled.li`
display: inline;
&:not(:last-child) {
margin-right: 1ex;
&::after {
content: ',';
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export const LogEntryColumn = euiStyled.div.attrs(() => ({
overflow: hidden;
`;

export const LogEntryColumnContent = euiStyled.div`
export const LogEntryColumnContent = euiStyled.div.attrs({
'data-test-subj': 'LogEntryColumnContent',
})`
flex: 1 0 0%;
padding: 2px ${COLUMN_PADDING}px;
`;
Expand Down
Loading

0 comments on commit 0bbb6d8

Please sign in to comment.