mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Logs UI] Actions menu in log entry categorization page (#69567)
This commit is contained in:
parent
0bae5d62c9
commit
21fc56ed10
8 changed files with 215 additions and 69 deletions
|
@ -12,6 +12,7 @@ import {
|
||||||
timeRangeRT,
|
timeRangeRT,
|
||||||
routeTimingMetadataRT,
|
routeTimingMetadataRT,
|
||||||
} from '../../shared';
|
} from '../../shared';
|
||||||
|
import { logEntryContextRT } from '../../log_entries';
|
||||||
|
|
||||||
export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH =
|
export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH =
|
||||||
'/api/infra/log_analysis/results/log_entry_category_examples';
|
'/api/infra/log_analysis/results/log_entry_category_examples';
|
||||||
|
@ -42,9 +43,12 @@ export type GetLogEntryCategoryExamplesRequestPayload = rt.TypeOf<
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const logEntryCategoryExampleRT = rt.type({
|
const logEntryCategoryExampleRT = rt.type({
|
||||||
|
id: rt.string,
|
||||||
dataset: rt.string,
|
dataset: rt.string,
|
||||||
message: rt.string,
|
message: rt.string,
|
||||||
timestamp: rt.number,
|
timestamp: rt.number,
|
||||||
|
tiebreaker: rt.number,
|
||||||
|
context: logEntryContextRT,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type LogEntryCategoryExample = rt.TypeOf<typeof logEntryCategoryExampleRT>;
|
export type LogEntryCategoryExample = rt.TypeOf<typeof logEntryCategoryExampleRT>;
|
||||||
|
|
|
@ -74,15 +74,17 @@ export const logMessageColumnRT = rt.type({
|
||||||
|
|
||||||
export const logColumnRT = rt.union([logTimestampColumnRT, logFieldColumnRT, logMessageColumnRT]);
|
export const logColumnRT = rt.union([logTimestampColumnRT, logFieldColumnRT, logMessageColumnRT]);
|
||||||
|
|
||||||
|
export const logEntryContextRT = rt.union([
|
||||||
|
rt.type({}),
|
||||||
|
rt.type({ 'container.id': rt.string }),
|
||||||
|
rt.type({ 'host.name': rt.string, 'log.file.path': rt.string }),
|
||||||
|
]);
|
||||||
|
|
||||||
export const logEntryRT = rt.type({
|
export const logEntryRT = rt.type({
|
||||||
id: rt.string,
|
id: rt.string,
|
||||||
cursor: logEntriesCursorRT,
|
cursor: logEntriesCursorRT,
|
||||||
columns: rt.array(logColumnRT),
|
columns: rt.array(logColumnRT),
|
||||||
context: rt.union([
|
context: logEntryContextRT,
|
||||||
rt.type({}),
|
|
||||||
rt.type({ 'container.id': rt.string }),
|
|
||||||
rt.type({ 'host.name': rt.string, 'log.file.path': rt.string }),
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type LogMessageConstantPart = rt.TypeOf<typeof logMessageConstantPartRT>;
|
export type LogMessageConstantPart = rt.TypeOf<typeof logMessageConstantPartRT>;
|
||||||
|
@ -92,6 +94,7 @@ export type LogTimestampColumn = rt.TypeOf<typeof logTimestampColumnRT>;
|
||||||
export type LogFieldColumn = rt.TypeOf<typeof logFieldColumnRT>;
|
export type LogFieldColumn = rt.TypeOf<typeof logFieldColumnRT>;
|
||||||
export type LogMessageColumn = rt.TypeOf<typeof logMessageColumnRT>;
|
export type LogMessageColumn = rt.TypeOf<typeof logMessageColumnRT>;
|
||||||
export type LogColumn = rt.TypeOf<typeof logColumnRT>;
|
export type LogColumn = rt.TypeOf<typeof logColumnRT>;
|
||||||
|
export type LogEntryContext = rt.TypeOf<typeof logEntryContextRT>;
|
||||||
export type LogEntry = rt.TypeOf<typeof logEntryRT>;
|
export type LogEntry = rt.TypeOf<typeof logEntryRT>;
|
||||||
|
|
||||||
export const logEntriesResponseRT = rt.type({
|
export const logEntriesResponseRT = rt.type({
|
||||||
|
|
|
@ -21,6 +21,8 @@ import {
|
||||||
StringTimeRange,
|
StringTimeRange,
|
||||||
useLogEntryCategoriesResultsUrlState,
|
useLogEntryCategoriesResultsUrlState,
|
||||||
} from './use_log_entry_categories_results_url_state';
|
} from './use_log_entry_categories_results_url_state';
|
||||||
|
import { PageViewLogInContext } from '../stream/page_view_log_in_context';
|
||||||
|
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
|
||||||
|
|
||||||
const JOB_STATUS_POLLING_INTERVAL = 30000;
|
const JOB_STATUS_POLLING_INTERVAL = 30000;
|
||||||
|
|
||||||
|
@ -178,6 +180,11 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ViewLogInContext.Provider
|
||||||
|
sourceId={sourceId}
|
||||||
|
startTimestamp={categoryQueryTimeRange.timeRange.startTime}
|
||||||
|
endTimestamp={categoryQueryTimeRange.timeRange.endTime}
|
||||||
|
>
|
||||||
<ResultsContentPage>
|
<ResultsContentPage>
|
||||||
<EuiFlexGroup direction="column">
|
<EuiFlexGroup direction="column">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
|
@ -226,6 +233,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</ResultsContentPage>
|
</ResultsContentPage>
|
||||||
|
<PageViewLogInContext />
|
||||||
|
</ViewLogInContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,13 @@ export const CategoryDetailsRow: React.FunctionComponent<{
|
||||||
{logEntryCategoryExamples.map((example, exampleIndex) => (
|
{logEntryCategoryExamples.map((example, exampleIndex) => (
|
||||||
<CategoryExampleMessage
|
<CategoryExampleMessage
|
||||||
key={exampleIndex}
|
key={exampleIndex}
|
||||||
|
id={example.id}
|
||||||
dataset={example.dataset}
|
dataset={example.dataset}
|
||||||
message={example.message}
|
message={example.message}
|
||||||
|
timeRange={timeRange}
|
||||||
timestamp={example.timestamp}
|
timestamp={example.timestamp}
|
||||||
|
tiebreaker={example.tiebreaker}
|
||||||
|
context={example.context}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</LogEntryExampleMessages>
|
</LogEntryExampleMessages>
|
||||||
|
|
|
@ -4,9 +4,18 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, useState, useCallback, useContext } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { encode } from 'rison-node';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis';
|
import { LogEntry, LogEntryContext } from '../../../../../../common/http_api';
|
||||||
|
import { TimeRange } from '../../../../../../common/http_api/shared';
|
||||||
|
import {
|
||||||
|
getFriendlyNameForPartitionId,
|
||||||
|
partitionField,
|
||||||
|
} from '../../../../../../common/log_analysis';
|
||||||
|
import { ViewLogInContext } from '../../../../../containers/logs/view_log_in_context';
|
||||||
import {
|
import {
|
||||||
LogEntryColumn,
|
LogEntryColumn,
|
||||||
LogEntryFieldColumn,
|
LogEntryFieldColumn,
|
||||||
|
@ -15,15 +24,22 @@ import {
|
||||||
LogEntryTimestampColumn,
|
LogEntryTimestampColumn,
|
||||||
} from '../../../../../components/logging/log_text_stream';
|
} from '../../../../../components/logging/log_text_stream';
|
||||||
import { LogColumnConfiguration } from '../../../../../utils/source_configuration';
|
import { LogColumnConfiguration } from '../../../../../utils/source_configuration';
|
||||||
|
import { LogEntryContextMenu } from '../../../../../components/logging/log_text_stream/log_entry_context_menu';
|
||||||
|
import { useLinkProps } from '../../../../../hooks/use_link_props';
|
||||||
|
|
||||||
export const exampleMessageScale = 'medium' as const;
|
export const exampleMessageScale = 'medium' as const;
|
||||||
export const exampleTimestampFormat = 'dateTime' as const;
|
export const exampleTimestampFormat = 'dateTime' as const;
|
||||||
|
|
||||||
export const CategoryExampleMessage: React.FunctionComponent<{
|
export const CategoryExampleMessage: React.FunctionComponent<{
|
||||||
|
id: string;
|
||||||
dataset: string;
|
dataset: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
timeRange: TimeRange;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}> = ({ dataset, message, timestamp }) => {
|
tiebreaker: number;
|
||||||
|
context: LogEntryContext;
|
||||||
|
}> = ({ id, dataset, message, timestamp, timeRange, tiebreaker, context }) => {
|
||||||
|
const [, { setContextEntry }] = useContext(ViewLogInContext.Context);
|
||||||
// the dataset must be encoded for the field column and the empty value must
|
// the dataset must be encoded for the field column and the empty value must
|
||||||
// be turned into a user-friendly value
|
// be turned into a user-friendly value
|
||||||
const encodedDatasetFieldValue = useMemo(
|
const encodedDatasetFieldValue = useMemo(
|
||||||
|
@ -31,8 +47,40 @@ export const CategoryExampleMessage: React.FunctionComponent<{
|
||||||
[dataset]
|
[dataset]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||||
|
const setHovered = useCallback(() => setIsHovered(true), []);
|
||||||
|
const setNotHovered = useCallback(() => setIsHovered(false), []);
|
||||||
|
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
||||||
|
const openMenu = useCallback(() => setIsMenuOpen(true), []);
|
||||||
|
const closeMenu = useCallback(() => setIsMenuOpen(false), []);
|
||||||
|
|
||||||
|
const viewInStreamLinkProps = useLinkProps({
|
||||||
|
app: 'logs',
|
||||||
|
pathname: 'stream',
|
||||||
|
search: {
|
||||||
|
logPosition: encode({
|
||||||
|
end: moment(timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||||
|
position: { tiebreaker, time: timestamp },
|
||||||
|
start: moment(timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||||
|
streamLive: false,
|
||||||
|
}),
|
||||||
|
flyoutOptions: encode({
|
||||||
|
surroundingLogsId: id,
|
||||||
|
}),
|
||||||
|
logFilter: encode({
|
||||||
|
expression: `${partitionField}: ${dataset}`,
|
||||||
|
kind: 'kuery',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogEntryRowWrapper scale={exampleMessageScale}>
|
<LogEntryRowWrapper
|
||||||
|
scale={exampleMessageScale}
|
||||||
|
onMouseEnter={setHovered}
|
||||||
|
onMouseLeave={setNotHovered}
|
||||||
|
>
|
||||||
<LogEntryColumn {...columnWidths[timestampColumnId]}>
|
<LogEntryColumn {...columnWidths[timestampColumnId]}>
|
||||||
<LogEntryTimestampColumn format={exampleTimestampFormat} time={timestamp} />
|
<LogEntryTimestampColumn format={exampleTimestampFormat} time={timestamp} />
|
||||||
</LogEntryColumn>
|
</LogEntryColumn>
|
||||||
|
@ -60,6 +108,39 @@ export const CategoryExampleMessage: React.FunctionComponent<{
|
||||||
wrapMode="none"
|
wrapMode="none"
|
||||||
/>
|
/>
|
||||||
</LogEntryColumn>
|
</LogEntryColumn>
|
||||||
|
<LogEntryColumn {...columnWidths[iconColumnId]}>
|
||||||
|
{isHovered || isMenuOpen ? (
|
||||||
|
<LogEntryContextMenu
|
||||||
|
isOpen={isMenuOpen}
|
||||||
|
onOpen={openMenu}
|
||||||
|
onClose={closeMenu}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: i18n.translate('xpack.infra.logs.categoryExample.viewInStreamText', {
|
||||||
|
defaultMessage: 'View in stream',
|
||||||
|
}),
|
||||||
|
onClick: viewInStreamLinkProps.onClick!,
|
||||||
|
href: viewInStreamLinkProps.href,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.translate('xpack.infra.logs.categoryExample.viewInContextText', {
|
||||||
|
defaultMessage: 'View in context',
|
||||||
|
}),
|
||||||
|
onClick: () => {
|
||||||
|
const logEntry: LogEntry = {
|
||||||
|
id,
|
||||||
|
context,
|
||||||
|
cursor: { time: timestamp, tiebreaker },
|
||||||
|
columns: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
setContextEntry(logEntry);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</LogEntryColumn>
|
||||||
</LogEntryRowWrapper>
|
</LogEntryRowWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -68,6 +149,7 @@ const noHighlights: never[] = [];
|
||||||
const timestampColumnId = 'category-example-timestamp-column' as const;
|
const timestampColumnId = 'category-example-timestamp-column' as const;
|
||||||
const messageColumnId = 'category-examples-message-column' as const;
|
const messageColumnId = 'category-examples-message-column' as const;
|
||||||
const datasetColumnId = 'category-examples-dataset-column' as const;
|
const datasetColumnId = 'category-examples-dataset-column' as const;
|
||||||
|
const iconColumnId = 'category-examples-icon-column' as const;
|
||||||
|
|
||||||
const columnWidths = {
|
const columnWidths = {
|
||||||
[timestampColumnId]: {
|
[timestampColumnId]: {
|
||||||
|
@ -85,7 +167,12 @@ const columnWidths = {
|
||||||
growWeight: 0,
|
growWeight: 0,
|
||||||
shrinkWeight: 0,
|
shrinkWeight: 0,
|
||||||
// w_dataset + w_max_anomaly + w_expand - w_padding = 200 px + 160 px + 40 px + 40 px - 8 px
|
// w_dataset + w_max_anomaly + w_expand - w_padding = 200 px + 160 px + 40 px + 40 px - 8 px
|
||||||
baseWidth: '432px',
|
baseWidth: '400px',
|
||||||
|
},
|
||||||
|
[iconColumnId]: {
|
||||||
|
growWeight: 0,
|
||||||
|
shrinkWeight: 0,
|
||||||
|
baseWidth: '32px',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ILegacyScopedClusterClient } from 'src/core/server';
|
import type { ILegacyScopedClusterClient } from 'src/core/server';
|
||||||
|
import { LogEntryContext } from '../../../common/http_api';
|
||||||
import {
|
import {
|
||||||
compareDatasetsByMaximumAnomalyScore,
|
compareDatasetsByMaximumAnomalyScore,
|
||||||
getJobId,
|
getJobId,
|
||||||
|
@ -43,6 +44,7 @@ import {
|
||||||
createTopLogEntryCategoriesQuery,
|
createTopLogEntryCategoriesQuery,
|
||||||
topLogEntryCategoriesResponseRT,
|
topLogEntryCategoriesResponseRT,
|
||||||
} from './queries/top_log_entry_categories';
|
} from './queries/top_log_entry_categories';
|
||||||
|
import { InfraSource } from '../sources';
|
||||||
|
|
||||||
const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000;
|
const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000;
|
||||||
|
|
||||||
|
@ -197,7 +199,8 @@ export async function getLogEntryCategoryExamples(
|
||||||
startTime: number,
|
startTime: number,
|
||||||
endTime: number,
|
endTime: number,
|
||||||
categoryId: number,
|
categoryId: number,
|
||||||
exampleCount: number
|
exampleCount: number,
|
||||||
|
sourceConfiguration: InfraSource
|
||||||
) {
|
) {
|
||||||
const finalizeLogEntryCategoryExamplesSpan = startTracingSpan('get category example log entries');
|
const finalizeLogEntryCategoryExamplesSpan = startTracingSpan('get category example log entries');
|
||||||
|
|
||||||
|
@ -215,6 +218,7 @@ export async function getLogEntryCategoryExamples(
|
||||||
const customSettings = decodeOrThrow(jobCustomSettingsRT)(mlJob.custom_settings);
|
const customSettings = decodeOrThrow(jobCustomSettingsRT)(mlJob.custom_settings);
|
||||||
const indices = customSettings?.logs_source_config?.indexPattern;
|
const indices = customSettings?.logs_source_config?.indexPattern;
|
||||||
const timestampField = customSettings?.logs_source_config?.timestampField;
|
const timestampField = customSettings?.logs_source_config?.timestampField;
|
||||||
|
const tiebreakerField = sourceConfiguration.configuration.fields.tiebreaker;
|
||||||
|
|
||||||
if (indices == null || timestampField == null) {
|
if (indices == null || timestampField == null) {
|
||||||
throw new InsufficientLogAnalysisMlJobConfigurationError(
|
throw new InsufficientLogAnalysisMlJobConfigurationError(
|
||||||
|
@ -239,6 +243,7 @@ export async function getLogEntryCategoryExamples(
|
||||||
context,
|
context,
|
||||||
indices,
|
indices,
|
||||||
timestampField,
|
timestampField,
|
||||||
|
tiebreakerField,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
category._source.terms,
|
category._source.terms,
|
||||||
|
@ -475,6 +480,7 @@ async function fetchLogEntryCategoryExamples(
|
||||||
requestContext: { core: { elasticsearch: { legacy: { client: ILegacyScopedClusterClient } } } },
|
requestContext: { core: { elasticsearch: { legacy: { client: ILegacyScopedClusterClient } } } },
|
||||||
indices: string,
|
indices: string,
|
||||||
timestampField: string,
|
timestampField: string,
|
||||||
|
tiebreakerField: string,
|
||||||
startTime: number,
|
startTime: number,
|
||||||
endTime: number,
|
endTime: number,
|
||||||
categoryQuery: string,
|
categoryQuery: string,
|
||||||
|
@ -490,6 +496,7 @@ async function fetchLogEntryCategoryExamples(
|
||||||
createLogEntryCategoryExamplesQuery(
|
createLogEntryCategoryExamplesQuery(
|
||||||
indices,
|
indices,
|
||||||
timestampField,
|
timestampField,
|
||||||
|
tiebreakerField,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
categoryQuery,
|
categoryQuery,
|
||||||
|
@ -502,9 +509,12 @@ async function fetchLogEntryCategoryExamples(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
examples: hits.map((hit) => ({
|
examples: hits.map((hit) => ({
|
||||||
|
id: hit._id,
|
||||||
dataset: hit._source.event?.dataset ?? '',
|
dataset: hit._source.event?.dataset ?? '',
|
||||||
message: hit._source.message ?? '',
|
message: hit._source.message ?? '',
|
||||||
timestamp: hit.sort[0],
|
timestamp: hit.sort[0],
|
||||||
|
tiebreaker: hit.sort[1],
|
||||||
|
context: getContextFromSource(hit._source),
|
||||||
})),
|
})),
|
||||||
timing: {
|
timing: {
|
||||||
spans: [esSearchSpan],
|
spans: [esSearchSpan],
|
||||||
|
@ -514,6 +524,22 @@ async function fetchLogEntryCategoryExamples(
|
||||||
|
|
||||||
const parseCategoryId = (rawCategoryId: string) => parseInt(rawCategoryId, 10);
|
const parseCategoryId = (rawCategoryId: string) => parseInt(rawCategoryId, 10);
|
||||||
|
|
||||||
|
const getContextFromSource = (source: any): LogEntryContext => {
|
||||||
|
const containerId = source.container?.id;
|
||||||
|
const hostName = source.host?.name;
|
||||||
|
const logFilePath = source.log?.file?.path;
|
||||||
|
|
||||||
|
if (typeof containerId === 'string') {
|
||||||
|
return { 'container.id': containerId };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof hostName === 'string' && typeof logFilePath === 'string') {
|
||||||
|
return { 'host.name': hostName, 'log.file.path': logFilePath };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
interface HistogramParameters {
|
interface HistogramParameters {
|
||||||
id: string;
|
id: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { defaultRequestParameters } from './common';
|
||||||
export const createLogEntryCategoryExamplesQuery = (
|
export const createLogEntryCategoryExamplesQuery = (
|
||||||
indices: string,
|
indices: string,
|
||||||
timestampField: string,
|
timestampField: string,
|
||||||
|
tiebreakerField: string,
|
||||||
startTime: number,
|
startTime: number,
|
||||||
endTime: number,
|
endTime: number,
|
||||||
categoryQuery: string,
|
categoryQuery: string,
|
||||||
|
@ -41,27 +42,33 @@ export const createLogEntryCategoryExamplesQuery = (
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sort: [
|
sort: [{ [timestampField]: 'asc' }, { [tiebreakerField]: 'asc' }],
|
||||||
{
|
|
||||||
[timestampField]: {
|
|
||||||
order: 'asc',
|
|
||||||
},
|
},
|
||||||
},
|
_source: ['event.dataset', 'message', 'container.id', 'host.name', 'log.file.path'],
|
||||||
],
|
|
||||||
},
|
|
||||||
_source: ['event.dataset', 'message'],
|
|
||||||
index: indices,
|
index: indices,
|
||||||
size: exampleCount,
|
size: exampleCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const logEntryCategoryExampleHitRT = rt.type({
|
export const logEntryCategoryExampleHitRT = rt.type({
|
||||||
|
_id: rt.string,
|
||||||
_source: rt.partial({
|
_source: rt.partial({
|
||||||
event: rt.partial({
|
event: rt.partial({
|
||||||
dataset: rt.string,
|
dataset: rt.string,
|
||||||
}),
|
}),
|
||||||
message: rt.string,
|
message: rt.string,
|
||||||
|
container: rt.partial({
|
||||||
|
id: rt.string,
|
||||||
}),
|
}),
|
||||||
sort: rt.tuple([rt.number]),
|
host: rt.partial({
|
||||||
|
name: rt.string,
|
||||||
|
}),
|
||||||
|
log: rt.partial({
|
||||||
|
file: rt.partial({
|
||||||
|
path: rt.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
sort: rt.tuple([rt.number, rt.number]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type LogEntryCategoryExampleHit = rt.TypeOf<typeof logEntryCategoryExampleHitRT>;
|
export type LogEntryCategoryExampleHit = rt.TypeOf<typeof logEntryCategoryExampleHitRT>;
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
} from '../../../lib/log_analysis';
|
} from '../../../lib/log_analysis';
|
||||||
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
import { assertHasInfraMlPlugins } from '../../../utils/request_context';
|
||||||
|
|
||||||
export const initGetLogEntryCategoryExamplesRoute = ({ framework }: InfraBackendLibs) => {
|
export const initGetLogEntryCategoryExamplesRoute = ({ framework, sources }: InfraBackendLibs) => {
|
||||||
framework.registerRoute(
|
framework.registerRoute(
|
||||||
{
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
@ -37,6 +37,11 @@ export const initGetLogEntryCategoryExamplesRoute = ({ framework }: InfraBackend
|
||||||
},
|
},
|
||||||
} = request.body;
|
} = request.body;
|
||||||
|
|
||||||
|
const sourceConfiguration = await sources.getSourceConfiguration(
|
||||||
|
requestContext.core.savedObjects.client,
|
||||||
|
sourceId
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertHasInfraMlPlugins(requestContext);
|
assertHasInfraMlPlugins(requestContext);
|
||||||
|
|
||||||
|
@ -46,7 +51,8 @@ export const initGetLogEntryCategoryExamplesRoute = ({ framework }: InfraBackend
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
categoryId,
|
categoryId,
|
||||||
exampleCount
|
exampleCount,
|
||||||
|
sourceConfiguration
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.ok({
|
return response.ok({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue