-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Logs UI] Fetch single log entries via a search strategy #81710
Changes from all commits
1982fd4
896656c
51c8458
ea1eb75
9e65553
79e555a
a6ceba1
b48e5de
edd4047
6a07bec
7d9cc2e
78a6bef
8bb0d80
2327000
d1c7b2d
0a43fb5
05f8212
7dac882
b402407
99a8d64
737ec78
856ae62
ca7681a
36a8a1c
3776445
35f8142
9cfe578
b24c3a6
c4e6e32
3fa56e0
8076833
f87b248
c80de2f
fb3b2b6
997fee0
ff57b2e
2fa8a93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ | |
*/ | ||
|
||
export * from './log_entry'; | ||
export * from './log_entry_cursor'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* 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 * as rt from 'io-ts'; | ||
import { decodeOrThrow } from '../runtime_types'; | ||
|
||
export const logEntryCursorRT = rt.type({ | ||
time: rt.number, | ||
tiebreaker: rt.number, | ||
}); | ||
|
||
export type LogEntryCursor = rt.TypeOf<typeof logEntryCursorRT>; | ||
|
||
export const getLogEntryCursorFromHit = (hit: { sort: [number, number] }) => | ||
decodeOrThrow(logEntryCursorRT)({ | ||
time: hit.sort[0], | ||
tiebreaker: hit.sort[1], | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,16 +7,41 @@ | |
import { fold } from 'fp-ts/lib/Either'; | ||
import { identity } from 'fp-ts/lib/function'; | ||
import { pipe } from 'fp-ts/lib/pipeable'; | ||
import { Errors, Type } from 'io-ts'; | ||
import { failure } from 'io-ts/lib/PathReporter'; | ||
import { RouteValidationFunction } from 'kibana/server'; | ||
import { Context, Errors, IntersectionType, Type, UnionType, ValidationError } from 'io-ts'; | ||
import type { RouteValidationFunction } from 'kibana/server'; | ||
|
||
type ErrorFactory = (message: string) => Error; | ||
|
||
const getErrorPath = ([first, ...rest]: Context): string[] => { | ||
if (typeof first === 'undefined') { | ||
return []; | ||
} else if (first.type instanceof IntersectionType) { | ||
const [, ...next] = rest; | ||
return getErrorPath(next); | ||
} else if (first.type instanceof UnionType) { | ||
const [, ...next] = rest; | ||
return [first.key, ...getErrorPath(next)]; | ||
} | ||
|
||
return [first.key, ...getErrorPath(rest)]; | ||
}; | ||
|
||
const getErrorType = ({ context }: ValidationError) => | ||
context[context.length - 1]?.type?.name ?? 'unknown'; | ||
|
||
const formatError = (error: ValidationError) => | ||
error.message ?? | ||
`in ${getErrorPath(error.context).join('/')}: ${JSON.stringify( | ||
error.value | ||
)} does not match expected type ${getErrorType(error)}`; | ||
|
||
const formatErrors = (errors: ValidationError[]) => | ||
`Failed to validate: \n${errors.map((error) => ` ${formatError(error)}`).join('\n')}`; | ||
Comment on lines
+15
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ℹ️ This aims to improve the readability of the io-ts validation error messages. |
||
|
||
export const createPlainError = (message: string) => new Error(message); | ||
|
||
export const throwErrors = (createError: ErrorFactory) => (errors: Errors) => { | ||
throw createError(failure(errors).join('\n')); | ||
throw createError(formatErrors(errors)); | ||
}; | ||
|
||
export const decodeOrThrow = <DecodedValue, EncodedValue, InputValue>( | ||
|
@@ -33,7 +58,7 @@ export const createValidationFunction = <DecodedValue, EncodedValue, InputValue> | |
pipe( | ||
runtimeType.decode(inputValue), | ||
fold<Errors, DecodedValue, ValdidationResult<DecodedValue>>( | ||
(errors: Errors) => badRequest(failure(errors).join('\n')), | ||
(errors: Errors) => badRequest(formatErrors(errors)), | ||
(result: DecodedValue) => ok(result) | ||
) | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* 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 * as rt from 'io-ts'; | ||
|
||
const genericErrorRT = rt.type({ | ||
type: rt.literal('generic'), | ||
message: rt.string, | ||
}); | ||
|
||
const shardFailureErrorRT = rt.type({ | ||
type: rt.literal('shardFailure'), | ||
shardInfo: rt.type({ | ||
shard: rt.number, | ||
index: rt.string, | ||
node: rt.string, | ||
}), | ||
message: rt.string, | ||
}); | ||
|
||
export const searchStrategyErrorRT = rt.union([genericErrorRT, shardFailureErrorRT]); | ||
|
||
export type SearchStrategyError = rt.TypeOf<typeof searchStrategyErrorRT>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* 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 * as rt from 'io-ts'; | ||
import { logEntryCursorRT } from '../../log_entry'; | ||
import { jsonArrayRT } from '../../typed_json'; | ||
import { searchStrategyErrorRT } from '../common/errors'; | ||
|
||
export const LOG_ENTRY_SEARCH_STRATEGY = 'infra-log-entry'; | ||
|
||
export const logEntrySearchRequestParamsRT = rt.type({ | ||
sourceId: rt.string, | ||
logEntryId: rt.string, | ||
}); | ||
|
||
export type LogEntrySearchRequestParams = rt.TypeOf<typeof logEntrySearchRequestParamsRT>; | ||
|
||
const logEntryFieldRT = rt.type({ | ||
field: rt.string, | ||
value: jsonArrayRT, | ||
}); | ||
|
||
export type LogEntryField = rt.TypeOf<typeof logEntryFieldRT>; | ||
|
||
export const logEntryRT = rt.type({ | ||
id: rt.string, | ||
index: rt.string, | ||
fields: rt.array(logEntryFieldRT), | ||
key: logEntryCursorRT, | ||
}); | ||
|
||
export type LogEntry = rt.TypeOf<typeof logEntryRT>; | ||
|
||
export const logEntrySearchResponsePayloadRT = rt.intersection([ | ||
rt.type({ | ||
data: rt.union([logEntryRT, rt.null]), | ||
}), | ||
rt.partial({ | ||
errors: rt.array(searchStrategyErrorRT), | ||
}), | ||
]); | ||
|
||
export type LogEntrySearchResponsePayload = rt.TypeOf<typeof logEntrySearchResponsePayloadRT>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ℹ️ Trying to avoid the unclear |
||
fields: [{ field: 'host.ip', value: ['HOST_IP'] }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
|
@@ -58,7 +58,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [{ field: 'container.id', value: ['CONTAINER_ID'] }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
|
@@ -88,7 +88,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [{ field: 'kubernetes.pod.uid', value: ['POD_UID'] }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
|
@@ -118,7 +118,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [ | ||
{ field: 'container.id', value: ['CONTAINER_ID'] }, | ||
{ field: 'host.ip', value: ['HOST_IP'] }, | ||
|
@@ -154,7 +154,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
|
@@ -188,7 +188,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [{ field: 'trace.id', value: ['1234567'] }], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
|
@@ -219,7 +219,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [ | ||
{ field: 'trace.id', value: ['1234567'] }, | ||
{ field: '@timestamp', value: [timestamp] }, | ||
|
@@ -252,7 +252,7 @@ describe('LogEntryActionsMenu component', () => { | |
const elementWrapper = mount( | ||
<ProviderWrapper> | ||
<LogEntryActionsMenu | ||
logItem={{ | ||
logEntry={{ | ||
fields: [], | ||
id: 'ITEM_ID', | ||
index: 'INDEX', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ Moving this out of the http directory to make it usable in search strategies as well.