Skip to content

Commit d0f471d

Browse files
committed
[Security Solution][Detections] - Fix remaining render and validation bug with query preview + tests (#80110)
## Summary This PR is meant to address the remaining rule query preview bugs. It addresses the following: - gives same loading experience for all rule query types (previously there were two different loading states) - makes sure noise warnings are showing for all query types and disappear on timeframe or query change - updates when to/from are set so it is set on preview click
1 parent ac27e68 commit d0f471d

File tree

32 files changed

+2569
-624
lines changed

32 files changed

+2569
-624
lines changed

x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
ExceptionListItemSchema,
1717
CreateExceptionListItemSchema,
1818
} from '../../../lists/common/schemas';
19+
import { ESBoolQuery } from '../typed_json';
1920
import { buildExceptionListQueries } from './build_exceptions_query';
2021
import {
2122
Query as QueryString,
@@ -31,7 +32,7 @@ export const getQueryFilter = (
3132
index: Index,
3233
lists: Array<ExceptionListItemSchema | CreateExceptionListItemSchema>,
3334
excludeExceptions: boolean = true
34-
) => {
35+
): ESBoolQuery => {
3536
const indexPattern: IIndexPattern = {
3637
fields: [],
3738
title: index.join(),

x-pack/plugins/security_solution/common/typed_json.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6+
import { DslQuery, Filter } from 'src/plugins/data/common';
7+
68
import { JsonObject } from '../../../../src/plugins/kibana_utils/common';
79

8-
export type ESQuery = ESRangeQuery | ESQueryStringQuery | ESMatchQuery | ESTermQuery | JsonObject;
10+
export type ESQuery =
11+
| ESRangeQuery
12+
| ESQueryStringQuery
13+
| ESMatchQuery
14+
| ESTermQuery
15+
| ESBoolQuery
16+
| JsonObject;
917

1018
export interface ESRangeQuery {
1119
range: {
@@ -37,3 +45,12 @@ export interface ESQueryStringQuery {
3745
export interface ESTermQuery {
3846
term: Record<string, string>;
3947
}
48+
49+
export interface ESBoolQuery {
50+
bool: {
51+
must: DslQuery[];
52+
filter: Filter[];
53+
should: never[];
54+
must_not: Filter[];
55+
};
56+
}

x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export type MatrixHistogramComponentProps = MatrixHistogramProps &
3434
defaultStackByOption: MatrixHistogramOption;
3535
errorMessage: string;
3636
headerChildren?: React.ReactNode;
37-
footerChildren?: React.ReactNode;
3837
hideHistogramIfEmpty?: boolean;
3938
histogramType: MatrixHistogramType;
4039
id: string;
@@ -48,7 +47,6 @@ export type MatrixHistogramComponentProps = MatrixHistogramProps &
4847
subtitle?: string | GetSubTitle;
4948
timelineId?: string;
5049
title: string | GetTitle;
51-
yTitle?: string | undefined;
5250
};
5351

5452
const DEFAULT_PANEL_HEIGHT = 300;
@@ -70,7 +68,6 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
7068
errorMessage,
7169
filterQuery,
7270
headerChildren,
73-
footerChildren,
7471
histogramType,
7572
hideHistogramIfEmpty = false,
7673
id,
@@ -89,7 +86,6 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
8986
title,
9087
titleSize,
9188
yTickFormatter,
92-
yTitle,
9389
}) => {
9490
const dispatch = useDispatch();
9591
const handleBrushEnd = useCallback(
@@ -118,18 +114,8 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
118114
onBrushEnd: handleBrushEnd,
119115
yTickFormatter,
120116
showLegend,
121-
yTitle,
122117
}),
123-
[
124-
chartHeight,
125-
startDate,
126-
legendPosition,
127-
endDate,
128-
handleBrushEnd,
129-
yTickFormatter,
130-
showLegend,
131-
yTitle,
132-
]
118+
[chartHeight, startDate, legendPosition, endDate, handleBrushEnd, yTickFormatter, showLegend]
133119
);
134120
const [isInitialLoading, setIsInitialLoading] = useState(true);
135121
const [selectedStackByOption, setSelectedStackByOption] = useState<MatrixHistogramOption>(
@@ -243,11 +229,6 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
243229
timelineId={timelineId}
244230
/>
245231
)}
246-
{footerChildren != null && (
247-
<EuiFlexGroup gutterSize="none" direction="row">
248-
{footerChildren}
249-
</EuiFlexGroup>
250-
)}
251232
</HistogramPanel>
252233
</InspectButtonContainer>
253234
{showSpacer && <EuiSpacer data-test-subj="spacer" size="l" />}

x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export interface MatrixHistogramQueryProps {
7171
startDate: string;
7272
histogramType: MatrixHistogramType;
7373
threshold?: { field: string | undefined; value: number } | undefined;
74+
skip?: boolean;
7475
}
7576

7677
export interface MatrixHistogramProps extends MatrixHistogramBasicProps {
@@ -105,7 +106,6 @@ export interface BarchartConfigs {
105106
yTickFormatter: TickFormatter;
106107
tickSize: number;
107108
};
108-
yAxisTitle: string | undefined;
109109
settings: {
110110
legendPosition: Position;
111111
onBrushEnd: UpdateDateRange;

x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ interface GetBarchartConfigsProps {
1919
onBrushEnd: UpdateDateRange;
2020
yTickFormatter?: (value: number) => string;
2121
showLegend?: boolean;
22-
yTitle?: string | undefined;
2322
}
2423

2524
export const DEFAULT_CHART_HEIGHT = 174;
@@ -33,7 +32,6 @@ export const getBarchartConfigs = ({
3332
onBrushEnd,
3433
yTickFormatter,
3534
showLegend,
36-
yTitle,
3735
}: GetBarchartConfigsProps): BarchartConfigs => ({
3836
series: {
3937
xScaleType: ScaleType.Time,
@@ -45,7 +43,6 @@ export const getBarchartConfigs = ({
4543
yTickFormatter: yTickFormatter != null ? yTickFormatter : DEFAULT_Y_TICK_FORMATTER,
4644
tickSize: 8,
4745
},
48-
yAxisTitle: yTitle,
4946
settings: {
5047
legendPosition: legendPosition ?? Position.Right,
5148
onBrushEnd,

x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ import { getInspectResponse } from '../../../helpers';
2727
import { InspectResponse } from '../../../types';
2828
import * as i18n from './translations';
2929

30+
export type Buckets = Array<{
31+
key: string;
32+
doc_count: number;
33+
}>;
34+
35+
const bucketEmpty: Buckets = [];
36+
3037
export interface UseMatrixHistogramArgs {
3138
data: MatrixHistogramData[];
3239
inspect: InspectResponse;
@@ -49,7 +56,12 @@ export const useMatrixHistogram = ({
4956
stackByField,
5057
startDate,
5158
threshold,
52-
}: MatrixHistogramQueryProps): [boolean, UseMatrixHistogramArgs] => {
59+
skip = false,
60+
}: MatrixHistogramQueryProps): [
61+
boolean,
62+
UseMatrixHistogramArgs,
63+
(to: string, from: string) => void
64+
] => {
5365
const { data, notifications } = useKibana().services;
5466
const refetch = useRef<inputsModel.Refetch>(noop);
5567
const abortCtrl = useRef(new AbortController());
@@ -98,10 +110,11 @@ export const useMatrixHistogram = ({
98110
next: (response) => {
99111
if (isCompleteResponse(response)) {
100112
if (!didCancel) {
101-
const histogramBuckets: Array<{
102-
key: string;
103-
doc_count: number;
104-
}> = getOr([], 'rawResponse.aggregations.eventActionGroup.buckets', response);
113+
const histogramBuckets: Buckets = getOr(
114+
bucketEmpty,
115+
'rawResponse.aggregations.eventActionGroup.buckets',
116+
response
117+
);
105118
setLoading(false);
106119
setMatrixHistogramResponse((prevResponse) => ({
107120
...prevResponse,
@@ -123,10 +136,12 @@ export const useMatrixHistogram = ({
123136
}
124137
},
125138
error: (msg) => {
139+
if (!didCancel) {
140+
setLoading(false);
141+
}
126142
if (!(msg instanceof AbortError)) {
127-
notifications.toasts.addDanger({
143+
notifications.toasts.addError(msg, {
128144
title: errorMessage ?? i18n.FAIL_MATRIX_HISTOGRAM,
129-
text: msg.message,
130145
});
131146
}
132147
},
@@ -166,8 +181,24 @@ export const useMatrixHistogram = ({
166181
}, [indexNames, endDate, filterQuery, startDate, stackByField, histogramType, threshold]);
167182

168183
useEffect(() => {
169-
hostsSearch(matrixHistogramRequest);
170-
}, [matrixHistogramRequest, hostsSearch]);
184+
if (!skip) {
185+
hostsSearch(matrixHistogramRequest);
186+
}
187+
}, [matrixHistogramRequest, hostsSearch, skip]);
188+
189+
const runMatrixHistogramSearch = useCallback(
190+
(to: string, from: string) => {
191+
hostsSearch({
192+
...matrixHistogramRequest,
193+
timerange: {
194+
interval: '12h',
195+
from,
196+
to,
197+
},
198+
});
199+
},
200+
[matrixHistogramRequest, hostsSearch]
201+
);
171202

172-
return [loading, matrixHistogramResponse];
203+
return [loading, matrixHistogramResponse, runMatrixHistogramSearch];
173204
};

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

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import { Unit } from '@elastic/datemath';
76

87
import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public';
98
import {
@@ -16,10 +15,6 @@ import {
1615
isErrorResponse,
1716
isValidationErrorResponse,
1817
} from '../../../../common/search_strategy/eql';
19-
import { getEqlAggsData, getSequenceAggs } from './helpers';
20-
import { EqlPreviewResponse, Source } from './types';
21-
import { hasEqlSequenceQuery } from '../../../../common/detection_engine/utils';
22-
import { EqlSearchResponse } from '../../../../common/detection_engine/types';
2318

2419
interface Params {
2520
index: string[];
@@ -56,66 +51,3 @@ export const validateEql = async ({
5651
return { valid: true, errors: [] };
5752
}
5853
};
59-
60-
interface AggsParams {
61-
data: DataPublicPluginStart;
62-
index: string[];
63-
interval: Unit;
64-
fromTime: string;
65-
query: string;
66-
toTime: string;
67-
signal: AbortSignal;
68-
}
69-
70-
export const getEqlPreview = async ({
71-
data,
72-
index,
73-
interval,
74-
query,
75-
fromTime,
76-
toTime,
77-
signal,
78-
}: AggsParams): Promise<EqlPreviewResponse> => {
79-
try {
80-
const response = await data.search
81-
.search<EqlSearchStrategyRequest, EqlSearchStrategyResponse<EqlSearchResponse<Source>>>(
82-
{
83-
params: {
84-
// @ts-expect-error allow_no_indices is missing on EqlSearch
85-
allow_no_indices: true,
86-
index: index.join(),
87-
body: {
88-
filter: {
89-
range: {
90-
'@timestamp': {
91-
gte: toTime,
92-
lte: fromTime,
93-
format: 'strict_date_optional_time',
94-
},
95-
},
96-
},
97-
query,
98-
// EQL requires a cap, otherwise it defaults to 10
99-
// It also sorts on ascending order, capping it at
100-
// something smaller like 20, made it so that some of
101-
// the more recent events weren't returned
102-
size: 100,
103-
},
104-
},
105-
},
106-
{
107-
strategy: 'eql',
108-
abortSignal: signal,
109-
}
110-
)
111-
.toPromise();
112-
113-
if (hasEqlSequenceQuery(query)) {
114-
return getSequenceAggs(response, interval, toTime, fromTime);
115-
} else {
116-
return getEqlAggsData(response, interval, toTime, fromTime);
117-
}
118-
} catch (err) {
119-
throw new Error(JSON.stringify(err));
120-
}
121-
};

0 commit comments

Comments
 (0)