Skip to content

Commit 1e2c94a

Browse files
rylndkibanamachine
andcommitted
[Search] Fixes EQL search strategy (#83064)
* Ensure that data is not lost when parsing EQL responses The shared search utilities expect that response data exists in the response's body field. However, in an EQL response this information also exists as a sibling to the body field, and so we must normalize this data into the body before we can leverage these utilities with EQL queries. * Remove unused EQL parameters These were previously needed to work around an index resolution but, but this has since been resolved upstream in elasticsearch via elastic/elasticsearch#63573. * Allow custom test subj for Preview Histogram to propagate to DOM Previously, custom preview histograms were passing a data-test-subj prop to our general histogram, but the general histogram did not know/care about this prop and it did not become a data property on the underlying DOM element. While most of our tests leveraged enzyme, they could still query by this react prop and everything worked as expected. However, now that we want to exercise this behavior in cypress, we need something to propagate to the DOM so that we can determine which histogram has rendered, so the prop has been updated to be `dataTestSubj`, which then becomes a data-test-subj on the histogram's panel. Tests have been updated accordingly. * Exercise Query Preview during EQL rule creation * Asserts that the preview displays a histogram * Asserts that no error toast is displayed * Add integration tests around EQL sequence signal generation * Clearer assertion * Simplify test assertion * Fix typings These were updated on an upstream refactor. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
1 parent 2e9fa46 commit 1e2c94a

18 files changed

Lines changed: 245 additions & 70 deletions

File tree

x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*/
66

77
import { of, merge, timer, throwError } from 'rxjs';
8-
import { takeWhile, switchMap, expand, mergeMap, tap } from 'rxjs/operators';
8+
import { map, takeWhile, switchMap, expand, mergeMap, tap } from 'rxjs/operators';
9+
import { ApiResponse } from '@elastic/elasticsearch';
910

1011
import {
1112
doSearch,
@@ -35,6 +36,15 @@ export const doPartialSearch = <SearchResponse = any>(
3536
takeWhile((response) => !isCompleteResponse(response), true)
3637
);
3738

39+
export const normalizeEqlResponse = <SearchResponse extends ApiResponse = ApiResponse>() =>
40+
map<SearchResponse, SearchResponse>((eqlResponse) => ({
41+
...eqlResponse,
42+
body: {
43+
...eqlResponse.body,
44+
...eqlResponse,
45+
},
46+
}));
47+
3848
export const throwOnEsError = () =>
3949
mergeMap((r: IKibanaSearchResponse) =>
4050
isErrorResponse(r) ? merge(of(r), throwError(new AbortError())) : of(r)

x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const getMockEqlResponse = () => ({
2222
sequences: [],
2323
},
2424
},
25+
meta: {},
26+
statusCode: 200,
2527
});
2628

2729
describe('EQL search strategy', () => {
@@ -193,5 +195,20 @@ describe('EQL search strategy', () => {
193195
expect(requestOptions).toEqual(expect.objectContaining({ ignore: [400] }));
194196
});
195197
});
198+
199+
describe('response', () => {
200+
it('contains a rawResponse field containing the full search response', async () => {
201+
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
202+
const response = await eqlSearch
203+
.search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockDeps)
204+
.toPromise();
205+
206+
expect(response).toEqual(
207+
expect.objectContaining({
208+
rawResponse: expect.objectContaining(getMockEqlResponse()),
209+
})
210+
);
211+
});
212+
});
196213
});
197214
});

x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import type { Logger } from 'kibana/server';
88
import type { ApiResponse } from '@elastic/elasticsearch';
99

1010
import { search } from '../../../../../src/plugins/data/server';
11-
import { doPartialSearch } from '../../common/search/es_search/es_search_rxjs_utils';
11+
import {
12+
doPartialSearch,
13+
normalizeEqlResponse,
14+
} from '../../common/search/es_search/es_search_rxjs_utils';
1215
import { getAsyncOptions, getDefaultSearchParams } from './get_default_search_params';
1316

1417
import type { ISearchStrategy, IEsRawSearchResponse } from '../../../../../src/plugins/data/server';
@@ -64,7 +67,7 @@ export const eqlSearchStrategyProvider = (
6467
(response) => response.body.id,
6568
request.id,
6669
options
67-
).pipe(utils.toKibanaSearchResponse());
70+
).pipe(normalizeEqlResponse(), utils.toKibanaSearchResponse());
6871
},
6972
};
7073
};

x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export const EQL_TYPE = '[data-test-subj="eqlRuleType"]';
5050

5151
export const EQL_QUERY_INPUT = '[data-test-subj="eqlQueryBarTextInput"]';
5252

53+
export const EQL_QUERY_PREVIEW_HISTOGRAM = '[data-test-subj="queryPreviewEqlHistogram"]';
54+
55+
export const EQL_QUERY_VALIDATION_SPINNER = '[data-test-subj="eql-validation-loading"]';
56+
5357
export const IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK =
5458
'[data-test-subj="importQueryFromSavedTimeline"]';
5559

@@ -80,6 +84,8 @@ export const MITRE_TACTIC_DROPDOWN = '[data-test-subj="mitreTactic"]';
8084
export const MITRE_TECHNIQUES_INPUT =
8185
'[data-test-subj="mitreTechniques"] [data-test-subj="comboBoxSearchInput"]';
8286

87+
export const QUERY_PREVIEW_BUTTON = '[data-test-subj="queryPreviewButton"]';
88+
8389
export const REFERENCE_URLS_INPUT =
8490
'[data-test-subj="detectionEngineStepAboutRuleReferenceUrls"] input';
8591

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export const NOTIFICATION_TOASTS = '[data-test-subj="globalToastList"]';
8+
9+
export const TOAST_ERROR_CLASS = 'euiToast--danger';

x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ import {
6161
THRESHOLD_TYPE,
6262
EQL_TYPE,
6363
EQL_QUERY_INPUT,
64+
QUERY_PREVIEW_BUTTON,
65+
EQL_QUERY_PREVIEW_HISTOGRAM,
66+
EQL_QUERY_VALIDATION_SPINNER,
6467
} from '../screens/create_new_rule';
68+
import { NOTIFICATION_TOASTS, TOAST_ERROR_CLASS } from '../screens/shared';
6569
import { TIMELINE } from '../screens/timelines';
6670
import { refreshPage } from './security_header';
6771

@@ -225,9 +229,12 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
225229

226230
export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => {
227231
cy.get(EQL_QUERY_INPUT).type(rule.customQuery);
228-
cy.get(EQL_QUERY_INPUT).invoke('text').should('eq', rule.customQuery);
229-
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
232+
cy.get(EQL_QUERY_VALIDATION_SPINNER).should('not.exist');
233+
cy.get(QUERY_PREVIEW_BUTTON).should('not.be.disabled').click({ force: true });
234+
cy.get(EQL_QUERY_PREVIEW_HISTOGRAM).should('contain.text', 'Hits');
235+
cy.get(NOTIFICATION_TOASTS).children().should('not.have.class', TOAST_ERROR_CLASS); // asserts no error toast on page
230236

237+
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
231238
cy.get(EQL_QUERY_INPUT).should('not.exist');
232239
};
233240

x-pack/plugins/security_solution/public/common/hooks/eql/api.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ export const validateEql = async ({
3232
const { rawResponse: response } = await data.search
3333
.search<EqlSearchStrategyRequest, EqlSearchStrategyResponse>(
3434
{
35-
// @ts-expect-error allow_no_indices is missing on EqlSearch
36-
params: { allow_no_indices: true, index: index.join(), body: { query, size: 0 } },
35+
params: { index: index.join(), body: { query, size: 0 } },
3736
options: { ignore: [400] },
3837
},
3938
{

x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ export const useEqlPreview = (): [
7474
.search<EqlSearchStrategyRequest, EqlSearchStrategyResponse<EqlSearchResponse<Source>>>(
7575
{
7676
params: {
77-
// @ts-expect-error allow_no_indices is missing on EqlSearch
78-
allow_no_indices: true,
7977
index: index.join(),
8078
body: {
8179
filter: {

x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.test.tsx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('PreviewCustomQueryHistogram', () => {
5151

5252
expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeTruthy();
5353
expect(
54-
wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').at(0).prop('subtitle')
54+
wrapper.find('[dataTestSubj="queryPreviewCustomHistogram"]').at(0).prop('subtitle')
5555
).toEqual(i18n.QUERY_PREVIEW_SUBTITLE_LOADING);
5656
});
5757

@@ -78,32 +78,32 @@ describe('PreviewCustomQueryHistogram', () => {
7878

7979
expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeFalsy();
8080
expect(
81-
wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').at(0).prop('subtitle')
81+
wrapper.find('[dataTestSubj="queryPreviewCustomHistogram"]').at(0).prop('subtitle')
8282
).toEqual(i18n.QUERY_PREVIEW_TITLE(9154));
83-
expect(
84-
wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').at(0).props().data
85-
).toEqual([
86-
{
87-
key: 'hits',
88-
value: [
89-
{
90-
g: 'All others',
91-
x: 1602247050000,
92-
y: 2314,
93-
},
94-
{
95-
g: 'All others',
96-
x: 1602247162500,
97-
y: 3471,
98-
},
99-
{
100-
g: 'All others',
101-
x: 1602247275000,
102-
y: 3369,
103-
},
104-
],
105-
},
106-
]);
83+
expect(wrapper.find('[dataTestSubj="queryPreviewCustomHistogram"]').at(0).props().data).toEqual(
84+
[
85+
{
86+
key: 'hits',
87+
value: [
88+
{
89+
g: 'All others',
90+
x: 1602247050000,
91+
y: 2314,
92+
},
93+
{
94+
g: 'All others',
95+
x: 1602247162500,
96+
y: 3471,
97+
},
98+
{
99+
g: 'All others',
100+
x: 1602247275000,
101+
y: 3369,
102+
},
103+
],
104+
},
105+
]
106+
);
107107
});
108108

109109
test('it invokes setQuery with id, inspect, isLoading and refetch', async () => {

x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const PreviewCustomQueryHistogram = ({
6969
subtitle={subtitle}
7070
disclaimer={i18n.QUERY_PREVIEW_DISCLAIMER}
7171
isLoading={isLoading}
72-
data-test-subj="queryPreviewCustomHistogram"
72+
dataTestSubj="queryPreviewCustomHistogram"
7373
/>
7474
);
7575
};

0 commit comments

Comments
 (0)