Skip to content

Commit

Permalink
Merge branch '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 57a1f25 + 29df2bd commit 5d96895
Show file tree
Hide file tree
Showing 37 changed files with 428 additions and 141 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
});
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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"
/>
);
}
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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',
}
);
Loading

0 comments on commit 5d96895

Please sign in to comment.