Skip to content

Commit f76d0df

Browse files
Alejandro Fernández Gómezweltenwort
andauthored
[7.10] [Logs UI] Transmit and render array field values in log entries (#81385) (#81893)
Co-authored-by: Alejandro Fernández Gómez <antarticonorte@gmail.com> Co-authored-by: Felix Stürmer <weltenwort@users.noreply.github.com>
1 parent 5ded1c0 commit f76d0df

40 files changed

+3963
-2965
lines changed

x-pack/plugins/infra/common/http_api/log_entries/entries.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import * as rt from 'io-ts';
8+
import { jsonArrayRT } from '../../typed_json';
89
import { logEntriesCursorRT } from './common';
910

1011
export const LOG_ENTRIES_PATH = '/api/log_entries/entries';
@@ -54,7 +55,7 @@ export const logMessageConstantPartRT = rt.type({
5455
});
5556
export const logMessageFieldPartRT = rt.type({
5657
field: rt.string,
57-
value: rt.unknown,
58+
value: jsonArrayRT,
5859
highlights: rt.array(rt.string),
5960
});
6061

@@ -64,7 +65,7 @@ export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt
6465
export const logFieldColumnRT = rt.type({
6566
columnId: rt.string,
6667
field: rt.string,
67-
value: rt.unknown,
68+
value: jsonArrayRT,
6869
highlights: rt.array(rt.string),
6970
});
7071
export const logMessageColumnRT = rt.type({

x-pack/plugins/infra/common/http_api/log_entries/item.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const logEntriesItemRequestRT = rt.type({
1616

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

19-
const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.string });
19+
const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.array(rt.string) });
2020
const logEntriesItemRT = rt.type({
2121
id: rt.string,
2222
index: rt.string,

x-pack/plugins/infra/common/typed_json.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

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

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

12-
export interface JsonObject {
13-
[key: string]: JsonValue;
14-
}
12+
export const jsonValueRT: rt.Type<JsonValue> = rt.recursion('JsonValue', () =>
13+
rt.union([jsonScalarRT, jsonArrayRT, jsonObjectRT])
14+
);
15+
16+
export const jsonArrayRT: rt.Type<JsonArray> = rt.recursion('JsonArray', () =>
17+
rt.array(jsonValueRT)
18+
);
19+
20+
export const jsonObjectRT: rt.Type<JsonObject> = rt.recursion('JsonObject', () =>
21+
rt.record(rt.string, jsonValueRT)
22+
);
23+
24+
export { JsonValue, JsonArray, JsonObject };

x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('LogEntryActionsMenu component', () => {
2929
<ProviderWrapper>
3030
<LogEntryActionsMenu
3131
logItem={{
32-
fields: [{ field: 'host.ip', value: 'HOST_IP' }],
32+
fields: [{ field: 'host.ip', value: ['HOST_IP'] }],
3333
id: 'ITEM_ID',
3434
index: 'INDEX',
3535
key: {
@@ -59,7 +59,7 @@ describe('LogEntryActionsMenu component', () => {
5959
<ProviderWrapper>
6060
<LogEntryActionsMenu
6161
logItem={{
62-
fields: [{ field: 'container.id', value: 'CONTAINER_ID' }],
62+
fields: [{ field: 'container.id', value: ['CONTAINER_ID'] }],
6363
id: 'ITEM_ID',
6464
index: 'INDEX',
6565
key: {
@@ -89,7 +89,7 @@ describe('LogEntryActionsMenu component', () => {
8989
<ProviderWrapper>
9090
<LogEntryActionsMenu
9191
logItem={{
92-
fields: [{ field: 'kubernetes.pod.uid', value: 'POD_UID' }],
92+
fields: [{ field: 'kubernetes.pod.uid', value: ['POD_UID'] }],
9393
id: 'ITEM_ID',
9494
index: 'INDEX',
9595
key: {
@@ -120,9 +120,9 @@ describe('LogEntryActionsMenu component', () => {
120120
<LogEntryActionsMenu
121121
logItem={{
122122
fields: [
123-
{ field: 'container.id', value: 'CONTAINER_ID' },
124-
{ field: 'host.ip', value: 'HOST_IP' },
125-
{ field: 'kubernetes.pod.uid', value: 'POD_UID' },
123+
{ field: 'container.id', value: ['CONTAINER_ID'] },
124+
{ field: 'host.ip', value: ['HOST_IP'] },
125+
{ field: 'kubernetes.pod.uid', value: ['POD_UID'] },
126126
],
127127
id: 'ITEM_ID',
128128
index: 'INDEX',
@@ -189,7 +189,7 @@ describe('LogEntryActionsMenu component', () => {
189189
<ProviderWrapper>
190190
<LogEntryActionsMenu
191191
logItem={{
192-
fields: [{ field: 'trace.id', value: '1234567' }],
192+
fields: [{ field: 'trace.id', value: ['1234567'] }],
193193
id: 'ITEM_ID',
194194
index: 'INDEX',
195195
key: {
@@ -221,8 +221,8 @@ describe('LogEntryActionsMenu component', () => {
221221
<LogEntryActionsMenu
222222
logItem={{
223223
fields: [
224-
{ field: 'trace.id', value: '1234567' },
225-
{ field: '@timestamp', value: timestamp },
224+
{ field: 'trace.id', value: ['1234567'] },
225+
{ field: '@timestamp', value: [timestamp] },
226226
],
227227
id: 'ITEM_ID',
228228
index: 'INDEX',

x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

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

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

@@ -97,12 +95,7 @@ const getUptimeLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
9795
.filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field))
9896
.reduce<string[]>((acc, fieldItem) => {
9997
const { field, value } = fieldItem;
100-
try {
101-
const parsedValue = decodeOrThrow(rt.array(rt.string))(JSON.parse(value));
102-
return acc.concat(parsedValue.map((val) => `${field}:${val}`));
103-
} catch (e) {
104-
return acc.concat([`${field}:${value}`]);
105-
}
98+
return acc.concat(value.map((val) => `${field}:${val}`));
10699
}, []);
107100

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

120113
const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
121114
const traceIdEntry = logItem.fields.find(
122-
({ field, value }) => value != null && field === 'trace.id'
115+
({ field, value }) => value[0] != null && field === 'trace.id'
123116
);
124117

125118
if (!traceIdEntry) {
126119
return undefined;
127120
}
128121

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

143136
return {
144137
app: 'apm',
145-
hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }),
138+
hash: getTraceUrl({ traceId: traceIdEntry.value[0], rangeFrom, rangeTo }),
146139
};
147140
};

x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export const LogEntryFlyout = ({
9494
onClick={createFilterHandler(item)}
9595
/>
9696
</EuiToolTip>
97-
{item.value}
97+
{formatValue(item.value)}
9898
</span>
9999
),
100100
},
@@ -147,3 +147,7 @@ export const InfraFlyoutLoadingPanel = euiStyled.div`
147147
bottom: 0;
148148
left: 0;
149149
`;
150+
151+
function formatValue(value: string[]) {
152+
return value.length > 1 ? value.join(', ') : value[0];
153+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import stringify from 'json-stable-stringify';
8+
import React from 'react';
9+
import { euiStyled } from '../../../../../observability/public';
10+
import { JsonArray, JsonValue } from '../../../../common/typed_json';
11+
import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting';
12+
13+
export const FieldValue: React.FC<{
14+
highlightTerms: string[];
15+
isActiveHighlight: boolean;
16+
value: JsonArray;
17+
}> = React.memo(({ highlightTerms, isActiveHighlight, value }) => {
18+
if (value.length === 1) {
19+
return (
20+
<>
21+
{highlightFieldValue(
22+
formatValue(value[0]),
23+
highlightTerms,
24+
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
25+
)}
26+
</>
27+
);
28+
} else if (value.length > 1) {
29+
return (
30+
<ul data-test-subj="LogEntryFieldValues">
31+
{value.map((entry, i) => (
32+
<CommaSeparatedLi
33+
key={`LogEntryFieldValue-${i}`}
34+
data-test-subj={`LogEntryFieldValue-${i}`}
35+
>
36+
{highlightFieldValue(
37+
formatValue(entry),
38+
highlightTerms,
39+
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
40+
)}
41+
</CommaSeparatedLi>
42+
))}
43+
</ul>
44+
);
45+
}
46+
47+
return null;
48+
});
49+
50+
const formatValue = (value: JsonValue): string => {
51+
if (typeof value === 'string') {
52+
return value;
53+
}
54+
55+
return stringify(value);
56+
};
57+
58+
const CommaSeparatedLi = euiStyled.li`
59+
display: inline;
60+
&:not(:last-child) {
61+
margin-right: 1ex;
62+
&::after {
63+
content: ',';
64+
}
65+
}
66+
`;

x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export const LogEntryColumn = euiStyled.div.attrs(() => ({
3939
overflow: hidden;
4040
`;
4141

42-
export const LogEntryColumnContent = euiStyled.div`
42+
export const LogEntryColumnContent = euiStyled.div.attrs({
43+
'data-test-subj': 'LogEntryColumnContent',
44+
})`
4345
flex: 1 0 0%;
4446
padding: 2px ${COLUMN_PADDING}px;
4547
`;

0 commit comments

Comments
 (0)