Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into eui/88.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
cee-chen committed Sep 19, 2023
2 parents 998a9a2 + 29df2bd commit 02d4f75
Show file tree
Hide file tree
Showing 37 changed files with 428 additions and 141 deletions.
Expand Up @@ -18,8 +18,9 @@ const getMessageEvent = (props: Partial<RuleExecutionEvent> = {}): RuleExecution
timestamp: DEFAULT_TIMESTAMP,
sequence: DEFAULT_SEQUENCE_NUMBER,
level: LogLevel.debug,
execution_id: 'execution-id-1',
message: 'Some message',
// Overriden values
// Overridden values
...props,
// Mandatory values for this type of event
type: RuleExecutionEventType.message,
Expand All @@ -31,8 +32,9 @@ const getRunningStatusChange = (props: Partial<RuleExecutionEvent> = {}): RuleEx
// Default values
timestamp: DEFAULT_TIMESTAMP,
sequence: DEFAULT_SEQUENCE_NUMBER,
execution_id: 'execution-id-1',
message: 'Rule changed status to "running"',
// Overriden values
// Overridden values
...props,
// Mandatory values for this type of event
level: LogLevel.info,
Expand All @@ -47,8 +49,9 @@ const getPartialFailureStatusChange = (
// Default values
timestamp: DEFAULT_TIMESTAMP,
sequence: DEFAULT_SEQUENCE_NUMBER,
execution_id: 'execution-id-1',
message: 'Rule changed status to "partial failure". Unknown error',
// Overriden values
// Overridden values
...props,
// Mandatory values for this type of event
level: LogLevel.warn,
Expand All @@ -61,8 +64,9 @@ const getFailedStatusChange = (props: Partial<RuleExecutionEvent> = {}): RuleExe
// Default values
timestamp: DEFAULT_TIMESTAMP,
sequence: DEFAULT_SEQUENCE_NUMBER,
execution_id: 'execution-id-1',
message: 'Rule changed status to "failed". Unknown error',
// Overriden values
// Overridden values
...props,
// Mandatory values for this type of event
level: LogLevel.error,
Expand All @@ -75,8 +79,9 @@ const getSucceededStatusChange = (props: Partial<RuleExecutionEvent> = {}): Rule
// Default values
timestamp: DEFAULT_TIMESTAMP,
sequence: DEFAULT_SEQUENCE_NUMBER,
execution_id: 'execution-id-1',
message: 'Rule changed status to "succeeded". Rule executed successfully',
// Overriden values
// Overridden values
...props,
// Mandatory values for this type of event
level: LogLevel.info,
Expand All @@ -89,8 +94,9 @@ const getExecutionMetricsEvent = (props: Partial<RuleExecutionEvent> = {}): Rule
// Default values
timestamp: DEFAULT_TIMESTAMP,
sequence: DEFAULT_SEQUENCE_NUMBER,
message: '',
// Overriden values
execution_id: 'execution-id-1',
message: JSON.stringify({ some_metric_ms: 10 }),
// Overridden values
...props,
// Mandatory values for this type of event
level: LogLevel.debug,
Expand Down
Expand Up @@ -6,7 +6,7 @@
*/

import * as t from 'io-ts';
import { enumeration, IsoDateString } from '@kbn/securitysolution-io-ts-types';
import { enumeration, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types';
import { enumFromString } from '../../../../utils/enum_from_string';
import { TLogLevel } from './log_level';

Expand Down Expand Up @@ -56,5 +56,6 @@ export const RuleExecutionEvent = t.type({
sequence: t.number,
level: TLogLevel,
type: TRuleExecutionEventType,
execution_id: NonEmptyString,
message: t.string,
});
Expand Up @@ -8,7 +8,7 @@
import * as t from 'io-ts';

import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types';
import { defaultCsvArray, NonEmptyString } from '@kbn/securitysolution-io-ts-types';
import { defaultCsvArray, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types';

import { DefaultSortOrderDesc, PaginationResult } from '../../../model';
import { RuleExecutionEvent, TRuleExecutionEventType, TLogLevel } from '../../model';
Expand All @@ -32,13 +32,20 @@ export type GetRuleExecutionEventsRequestQuery = t.TypeOf<
typeof GetRuleExecutionEventsRequestQuery
>;
export const GetRuleExecutionEventsRequestQuery = t.exact(
t.type({
event_types: defaultCsvArray(TRuleExecutionEventType),
log_levels: defaultCsvArray(TLogLevel),
sort_order: DefaultSortOrderDesc, // defaults to 'desc'
page: DefaultPage, // defaults to 1
per_page: DefaultPerPage, // defaults to 20
})
t.intersection([
t.partial({
search_term: NonEmptyString,
event_types: defaultCsvArray(TRuleExecutionEventType),
log_levels: defaultCsvArray(TLogLevel),
date_start: IsoDateString,
date_end: IsoDateString,
}),
t.type({
sort_order: DefaultSortOrderDesc, // defaults to 'desc'
page: DefaultPage, // defaults to 1
per_page: DefaultPerPage, // defaults to 20
}),
])
);

/**
Expand Down
Expand Up @@ -32,6 +32,7 @@ export const api: jest.Mocked<IRuleMonitoringApiClient> = {
sequence: 0,
level: LogLevel.info,
type: RuleExecutionEventType['status-change'],
execution_id: 'execution-id-1',
message: 'Rule changed status to "succeeded". Rule execution completed without errors',
},
],
Expand Down
Expand Up @@ -72,26 +72,59 @@ describe('Rule Monitoring API Client', () => {
);
});

it('calls API correctly with filter and pagination options', async () => {
const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;

it.each([
[
'search term filter',
{ searchTerm: 'something to search' },
{ search_term: 'something to search' },
],
[
'event types filter',
{ eventTypes: [RuleExecutionEventType.message] },
{ event_types: 'message' },
],
[
'log level filter',
{ logLevels: [LogLevel.warn, LogLevel.error] },
{ log_levels: 'warn,error' },
],
[
'start date filter in relative format',
{ dateRange: { start: 'now-1d/d' } },
{ date_start: expect.stringMatching(ISO_PATTERN) },
],
[
'end date filter',
{ dateRange: { end: 'now-3d/d' } },
{ date_end: expect.stringMatching(ISO_PATTERN) },
],
[
'date range filter in relative format',
{ dateRange: { start: new Date().toISOString(), end: new Date().toISOString() } },
{
date_start: expect.stringMatching(ISO_PATTERN),
date_end: expect.stringMatching(ISO_PATTERN),
},
],
[
'pagination',
{ sortOrder: 'asc', page: 42, perPage: 146 } as const,
{ sort_order: 'asc', page: 42, per_page: 146 },
],
])('calls API correctly with %s', async (_, params, expectedParams) => {
await api.fetchRuleExecutionEvents({
ruleId: '42',
eventTypes: [RuleExecutionEventType.message],
logLevels: [LogLevel.warn, LogLevel.error],
sortOrder: 'asc',
page: 42,
perPage: 146,
...params,
});

expect(fetchMock).toHaveBeenCalledWith(
'/internal/detection_engine/rules/42/execution/events',
expect.objectContaining({
method: 'GET',
query: {
event_types: 'message',
log_levels: 'warn,error',
sort_order: 'asc',
page: 42,
per_page: 146,
...expectedParams,
},
})
);
Expand Down
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { omitBy, isUndefined } from 'lodash';
import dateMath from '@kbn/datemath';

import { KibanaServices } from '../../../common/lib/kibana';
Expand Down Expand Up @@ -36,20 +37,38 @@ export const api: IRuleMonitoringApiClient = {
fetchRuleExecutionEvents: (
args: FetchRuleExecutionEventsArgs
): Promise<GetRuleExecutionEventsResponse> => {
const { ruleId, eventTypes, logLevels, sortOrder, page, perPage, signal } = args;
const {
ruleId,
searchTerm,
eventTypes,
logLevels,
dateRange,
sortOrder,
page,
perPage,
signal,
} = args;

const url = getRuleExecutionEventsUrl(ruleId);
const startDate = dateMath.parse(dateRange?.start ?? '')?.toISOString();
const endDate = dateMath.parse(dateRange?.end ?? '', { roundUp: true })?.toISOString();

return http().fetch<GetRuleExecutionEventsResponse>(url, {
method: 'GET',
version: '1',
query: {
event_types: eventTypes?.join(','),
log_levels: logLevels?.join(','),
sort_order: sortOrder,
page,
per_page: perPage,
},
query: omitBy(
{
search_term: searchTerm?.length ? searchTerm : undefined,
event_types: eventTypes?.length ? eventTypes.join(',') : undefined,
log_levels: logLevels?.length ? logLevels.join(',') : undefined,
date_start: startDate,
date_end: endDate,
sort_order: sortOrder,
page,
per_page: perPage,
},
isUndefined
),
signal,
});
},
Expand Down
Expand Up @@ -46,12 +46,22 @@ export interface RuleMonitoringApiCallArgs {
signal?: AbortSignal;
}

export interface DateRange {
start?: string;
end?: string;
}

export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs {
/**
* Saved Object ID of the rule (`rule.id`, not static `rule.rule_id`).
*/
ruleId: string;

/**
* Filter by event message. If set, result will include events matching the search term.
*/
searchTerm?: string;

/**
* Filter by event type. If set, result will include only events matching any of these.
*/
Expand All @@ -62,6 +72,11 @@ export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs
*/
logLevels?: LogLevel[];

/**
* Filter by date range. If set, result will include only events recorded in the specified date range.
*/
dateRange?: DateRange;

/**
* What order to sort by (e.g. `asc` or `desc`).
*/
Expand Down
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { ChangeEvent } from 'react';
import React, { useCallback } from 'react';
import { EuiFieldSearch } from '@elastic/eui';
import * as i18n from './translations';

interface EventMessageFilterProps {
value: string;
onChange: (value: string) => void;
}

export function EventMessageFilter({ value, onChange }: EventMessageFilterProps): JSX.Element {
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value),
[onChange]
);

return (
<EuiFieldSearch
aria-label={i18n.SEARCH_BY_EVENT_MESSAGE_ARIA_LABEL}
fullWidth
incremental={false}
placeholder={i18n.SEARCH_BY_EVENT_MESSAGE_PLACEHOLDER}
value={value}
onChange={handleChange}
data-test-subj="ruleEventLogMessageSearchField"
/>
);
}
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './event_message_filter';
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';

export const SEARCH_BY_EVENT_MESSAGE_ARIA_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.eventLog.searchAriaLabel',
{
defaultMessage: 'Search by event message',
}
);

export const SEARCH_BY_EVENT_MESSAGE_PLACEHOLDER = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.eventLog.searchPlaceholder',
{
defaultMessage: 'Search by event message',
}
);

0 comments on commit 02d4f75

Please sign in to comment.