Skip to content

Commit

Permalink
[SECURITY SOLUTION] Eql in timeline (#90816)
Browse files Browse the repository at this point in the history
* add EQL as a language

* add eql in timeline

* fix type + unit test

* move eql to it sown tab

* fix merge issue + a liitle bug when creating anew timeline to reset eql textarea

* fix cypress tests

* fix lint error

* fix bug from review

Co-authored-by: Angela Chuang <yi-chun.chuang@elastic.co>
  • Loading branch information
XavierM and angorayc authored Feb 17, 2021
1 parent d7d2b15 commit 4707dae
Show file tree
Hide file tree
Showing 67 changed files with 4,435 additions and 199 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/common/ecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ export interface Ecs {
process?: ProcessEcs;
file?: FileEcs;
system?: SystemEcs;
// This should be temporary
eql?: { parentId: string; sequenceNumber: string };
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse {
export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsPaginated {
fields: string[];
fieldRequested: string[];
language: 'eql' | 'kuery' | 'lucene';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { EuiComboBoxOptionOption } from '@elastic/eui';
import {
EqlSearchStrategyRequest,
EqlSearchStrategyResponse,
} from '../../../../../../data_enhanced/common';
import { Inspect, Maybe, PaginationInputPaginated } from '../../..';
import { TimelineEdges, TimelineEventsAllRequestOptions } from '../..';
import { EqlSearchResponse } from '../../../../detection_engine/types';

export interface TimelineEqlRequestOptions
extends EqlSearchStrategyRequest,
Omit<TimelineEventsAllRequestOptions, 'params'> {
eventCategoryField?: string;
tiebreakerField?: string;
timestampField?: string;
size?: number;
}

export interface TimelineEqlResponse extends EqlSearchStrategyResponse<EqlSearchResponse<unknown>> {
edges: TimelineEdges[];
totalCount: number;
pageInfo: Pick<PaginationInputPaginated, 'activePage' | 'querySize'>;
inspect: Maybe<Inspect>;
}

export interface EqlOptionsData {
keywordFields: EuiComboBoxOptionOption[];
dateFields: EuiComboBoxOptionOption[];
nonDateFields: EuiComboBoxOptionOption[];
}

export interface EqlOptionsSelected {
eventCategoryField?: string;
tiebreakerField?: string;
timestampField?: string;
query?: string;
size?: number;
}

export type FieldsEqlOptions = keyof EqlOptionsSelected;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
export * from './all';
export * from './details';
export * from './last_event_time';
export * from './eql';

export enum TimelineEventsQueries {
all = 'eventsAll',
Expand Down
15 changes: 14 additions & 1 deletion x-pack/plugins/security_solution/common/types/timeline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ const SavedFilterRuntimeType = runtimeTypes.partial({
script: unionWithNullType(runtimeTypes.string),
});

/*
* eqlOptionsQuery -> filterQuery Types
*/
const EqlOptionsRuntimeType = runtimeTypes.partial({
eventCategoryField: unionWithNullType(runtimeTypes.string),
query: unionWithNullType(runtimeTypes.string),
tiebreakerField: unionWithNullType(runtimeTypes.string),
timestampField: unionWithNullType(runtimeTypes.string),
size: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])),
});

/*
* kqlQuery -> filterQuery Types
*/
Expand Down Expand Up @@ -246,6 +257,7 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
columns: unionWithNullType(runtimeTypes.array(SavedColumnHeaderRuntimeType)),
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
description: unionWithNullType(runtimeTypes.string),
eqlOptions: unionWithNullType(EqlOptionsRuntimeType),
eventType: unionWithNullType(runtimeTypes.string),
excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)),
favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)),
Expand Down Expand Up @@ -413,13 +425,14 @@ export const importTimelineResultSchema = runtimeTypes.exact(

export type ImportTimelineResultSchema = runtimeTypes.TypeOf<typeof importTimelineResultSchema>;

export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom';
export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom' | 'eql';

export enum TimelineTabs {
query = 'query',
graph = 'graph',
notes = 'notes',
pinned = 'pinned',
eql = 'eql',
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export const RISK_MAPPING_OVERRIDE_OPTION = '#risk_score-mapping-override';
export const RISK_OVERRIDE =
'[data-test-subj="detectionEngineStepAboutRuleRiskScore-riskOverride"]';

export const RULES_CREATION_FORM = '[data-test-subj="stepDefineRule"]';

export const RULES_CREATION_PREVIEW = '[data-test-subj="ruleCreationQueryPreview"]';

export const RULE_DESCRIPTION_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleDescription"] [data-test-subj="input"]';

Expand Down
21 changes: 13 additions & 8 deletions x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ import {
AT_LEAST_ONE_VALID_MATCH,
AT_LEAST_ONE_INDEX_PATTERN,
CUSTOM_QUERY_REQUIRED,
RULES_CREATION_FORM,
RULES_CREATION_PREVIEW,
} from '../screens/create_new_rule';
import { TOAST_ERROR } from '../screens/shared';
import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline';
Expand Down Expand Up @@ -271,23 +273,26 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
};

export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => {
cy.get(EQL_QUERY_INPUT).should('exist');
cy.get(EQL_QUERY_INPUT).should('be.visible');
cy.get(EQL_QUERY_INPUT).type(rule.customQuery!);
cy.get(EQL_QUERY_VALIDATION_SPINNER).should('not.exist');
cy.get(QUERY_PREVIEW_BUTTON).should('not.be.disabled').click({ force: true });
cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).should('exist');
cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).should('be.visible');
cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).type(rule.customQuery!);
cy.get(RULES_CREATION_FORM).find(EQL_QUERY_VALIDATION_SPINNER).should('not.exist');
cy.get(RULES_CREATION_PREVIEW)
.find(QUERY_PREVIEW_BUTTON)
.should('not.be.disabled')
.click({ force: true });
cy.get(EQL_QUERY_PREVIEW_HISTOGRAM)
.invoke('text')
.then((text) => {
if (text !== 'Hits') {
cy.get(QUERY_PREVIEW_BUTTON).click({ force: true });
cy.get(RULES_CREATION_PREVIEW).find(QUERY_PREVIEW_BUTTON).click({ force: true });
cy.get(EQL_QUERY_PREVIEW_HISTOGRAM).should('contain.text', 'Hits');
}
});
cy.get(TOAST_ERROR).should('not.exist');

cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
cy.get(EQL_QUERY_INPUT).should('not.exist');
cy.get(`${RULES_CREATION_FORM} ${EQL_QUERY_INPUT}`).should('not.exist');
};

/**
Expand Down Expand Up @@ -480,7 +485,7 @@ export const waitForAlertsToPopulate = async () => {

export const waitForTheRuleToBeExecuted = () => {
cy.waitUntil(() => {
cy.get(REFRESH_BUTTON).click();
cy.get(REFRESH_BUTTON).click({ force: true });
return cy
.get(RULE_STATUS)
.invoke('text')
Expand Down
5 changes: 3 additions & 2 deletions x-pack/plugins/security_solution/cypress/tasks/date_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const setStartDate = (date: string) => {
};

export const setTimelineEndDate = (date: string) => {
cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE).click({ force: true });
cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE).first().click({ force: true });

cy.get(DATE_PICKER_ABSOLUTE_TAB).first().click({ force: true });

Expand All @@ -47,7 +47,7 @@ export const setTimelineEndDate = (date: string) => {
};

export const setTimelineStartDate = (date: string) => {
cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).click({
cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).first().click({
force: true,
});

Expand All @@ -68,6 +68,7 @@ export const updateDates = () => {

export const updateTimelineDates = () => {
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE)
.first()
.click({ force: true })
.should('not.have.text', 'Updating');
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { useReducer, useCallback, useRef, useEffect } from 'react';

import { CasePostRequest } from '../../../../case/common/api';
import { errorToToaster, useStateToaster } from '../../common/components/toasters';
import { postCase } from './api';
Expand All @@ -16,6 +17,7 @@ interface NewCaseState {
isError: boolean;
}
type Action = { type: 'FETCH_INIT' } | { type: 'FETCH_SUCCESS' } | { type: 'FETCH_FAILURE' };

const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => {
switch (action.type) {
case 'FETCH_INIT':
Expand Down Expand Up @@ -76,6 +78,7 @@ export const usePostCase = (): UsePostCase => {
},
[dispatchToaster]
);

useEffect(() => {
return () => {
abortCtrl.current.abort();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ export const useInitSourcerer = (
selectedPatterns: [...ConfigIndexPatterns, signalIndexName],
})
);
} else if (signalIndexNameSelector != null && initialTimelineSourcerer.current) {
} else if (
signalIndexNameSelector != null &&
(activeTimeline == null ||
(activeTimeline != null && activeTimeline.savedObjectId == null)) &&
initialTimelineSourcerer.current
) {
initialTimelineSourcerer.current = false;
dispatch(
sourcererActions.setSelectedIndexPatterns({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,28 @@ import { createPortalNode, OutPortal } from 'react-reverse-portal';
* A singleton portal for rendering content in the global header
*/
const timelineEventsCountPortalNodeSingleton = createPortalNode();
const eqlEventsCountPortalNodeSingleton = createPortalNode();

export const useTimelineEventsCountPortal = () => {
const [timelineEventsCountPortalNode] = useState(timelineEventsCountPortalNodeSingleton);

return { timelineEventsCountPortalNode };
return { portalNode: timelineEventsCountPortalNode };
};

export const TimelineEventsCountBadge = React.memo(() => {
const { timelineEventsCountPortalNode } = useTimelineEventsCountPortal();

return <OutPortal node={timelineEventsCountPortalNode} />;
const { portalNode } = useTimelineEventsCountPortal();
return <OutPortal node={portalNode} />;
});

TimelineEventsCountBadge.displayName = 'TimelineEventsCountBadge';

export const useEqlEventsCountPortal = () => {
const [eqlEventsCountPortalNode] = useState(eqlEventsCountPortalNodeSingleton);
return { portalNode: eqlEventsCountPortalNode };
};

export const EqlEventsCountBadge = React.memo(() => {
const { portalNode } = useEqlEventsCountPortal();
return <OutPortal node={portalNode} />;
});

EqlEventsCountBadge.displayName = 'EqlEventsCountBadge';
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ export const mockGlobalState: State = {
itemsPerPage: 5,
dataProviders: [],
description: '',
eqlOptions: {
eventCategoryField: 'event.category',
tiebreakerField: 'event.sequence',
timestampField: '@timestamp',
},
eventIdToNoteIds: {},
excludedRowRendererIds: [],
expandedDetail: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,11 @@ export const mockTimelineModel: TimelineModel = {
},
deletedEventIds: [],
description: 'This is a sample rule description',
eqlOptions: {
eventCategoryField: 'event.category',
tiebreakerField: 'event.sequence',
timestampField: '@timestamp',
},
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
Expand Down Expand Up @@ -2229,6 +2234,13 @@ export const defaultTimelineProps: CreateTimelineProps = {
dateRange: { end: '2018-11-05T19:03:25.937Z', start: '2018-11-05T18:58:25.937Z' },
deletedEventIds: [],
description: '',
eqlOptions: {
eventCategoryField: 'event.category',
query: '',
size: 100,
tiebreakerField: 'event.sequence',
timestampField: '@timestamp',
},
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export type StoreState = HostsPluginState &
*/
export type State = CombinedState<StoreState>;

export type KueryFilterQueryKind = 'kuery' | 'lucene';
export type KueryFilterQueryKind = 'kuery' | 'lucene' | 'eql';

export interface KueryFilterQuery {
kind: KueryFilterQueryKind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ describe('alert actions', () => {
},
deletedEventIds: [],
description: 'This is a sample rule description',
eqlOptions: {
eventCategoryField: 'event.category',
query: '',
size: 100,
tiebreakerField: 'event.sequence',
timestampField: '@timestamp',
},
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
Expand Down
Loading

0 comments on commit 4707dae

Please sign in to comment.