Skip to content

Commit 815ce3d

Browse files
[SECURITY SOLUTIONS] Bugs overview page + investigate eql in timeline (#81550)
* fix overview query to be connected to sourcerer * investigate eql in timeline * keep timeline indices * trusting what is coming from timeline saved object for index pattern at initialization * fix type + initialize old timeline to sourcerer Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 2a882c0 commit 815ce3d

10 files changed

Lines changed: 184 additions & 38 deletions

File tree

x-pack/plugins/security_solution/public/common/containers/source/index.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,14 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => {
194194
const { data, notifications } = useKibana().services;
195195
const abortCtrl = useRef(new AbortController());
196196
const dispatch = useDispatch();
197-
const previousIndexesName = useRef<string[]>([]);
198-
199197
const indexNamesSelectedSelector = useMemo(
200198
() => sourcererSelectors.getIndexNamesSelectedSelector(),
201199
[]
202200
);
203-
const indexNames = useShallowEqualSelector<string[]>((state) =>
204-
indexNamesSelectedSelector(state, sourcererScopeName)
205-
);
201+
const { indexNames, previousIndexNames } = useShallowEqualSelector<{
202+
indexNames: string[];
203+
previousIndexNames: string;
204+
}>((state) => indexNamesSelectedSelector(state, sourcererScopeName));
206205

207206
const setLoading = useCallback(
208207
(loading: boolean) => {
@@ -230,7 +229,6 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => {
230229
if (!response.isPartial && !response.isRunning) {
231230
if (!didCancel) {
232231
const stringifyIndices = response.indicesExist.sort().join();
233-
previousIndexesName.current = response.indicesExist;
234232
dispatch(
235233
sourcererActions.setSource({
236234
id: sourcererScopeName,
@@ -279,8 +277,8 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => {
279277
);
280278

281279
useEffect(() => {
282-
if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) {
280+
if (!isEmpty(indexNames) && previousIndexNames !== indexNames.sort().join()) {
283281
indexFieldsSearch(indexNames);
284282
}
285-
}, [indexNames, indexFieldsSearch, previousIndexesName]);
283+
}, [indexNames, indexFieldsSearch, previousIndexNames]);
286284
};

x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Provider } from 'react-redux';
1212

1313
import { useInitSourcerer } from '.';
1414
import { mockPatterns, mockSource } from './mocks';
15-
// import { SourcererScopeName } from '../../store/sourcerer/model';
15+
import { SourcererScopeName } from '../../store/sourcerer/model';
1616
import { RouteSpyState } from '../../utils/route/types';
1717
import { SecurityPageName } from '../../../../common/constants';
1818
import { createStore, State } from '../../store';
@@ -85,7 +85,29 @@ describe('Sourcerer Hooks', () => {
8585
jest.clearAllMocks();
8686
jest.restoreAllMocks();
8787
});
88-
const state: State = mockGlobalState;
88+
const state: State = {
89+
...mockGlobalState,
90+
sourcerer: {
91+
...mockGlobalState.sourcerer,
92+
sourcererScopes: {
93+
...mockGlobalState.sourcerer.sourcererScopes,
94+
[SourcererScopeName.default]: {
95+
...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default],
96+
indexPattern: {
97+
fields: [],
98+
title: '',
99+
},
100+
},
101+
[SourcererScopeName.timeline]: {
102+
...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline],
103+
indexPattern: {
104+
fields: [],
105+
title: '',
106+
},
107+
},
108+
},
109+
},
110+
};
89111
const { storage } = createSecuritySolutionStorageMock();
90112
let store = createStore(
91113
state,

x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import { ManageScope, SourcererScopeName } from '../../store/sourcerer/model';
1616
import { useIndexFields } from '../source';
1717
import { State } from '../../store';
1818
import { useUserInfo } from '../../../detections/components/user_info';
19+
import { timelineSelectors } from '../../../timelines/store/timeline';
20+
import { TimelineId } from '../../../../common/types/timeline';
21+
import { TimelineModel } from '../../../timelines/store/timeline/model';
1922

2023
export const useInitSourcerer = (
2124
scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default
@@ -29,6 +32,12 @@ export const useInitSourcerer = (
2932
);
3033
const ConfigIndexPatterns = useSelector(getConfigIndexPatternsSelector, isEqual);
3134

35+
const getTimelineSelector = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
36+
const activeTimeline = useSelector<State, TimelineModel>(
37+
(state) => getTimelineSelector(state, TimelineId.active),
38+
isEqual
39+
);
40+
3241
useIndexFields(scopeId);
3342
useIndexFields(SourcererScopeName.timeline);
3443

@@ -40,15 +49,19 @@ export const useInitSourcerer = (
4049

4150
// Related to timeline
4251
useEffect(() => {
43-
if (!loadingSignalIndex && signalIndexName != null) {
52+
if (
53+
!loadingSignalIndex &&
54+
signalIndexName != null &&
55+
(activeTimeline == null || (activeTimeline != null && activeTimeline.savedObjectId == null))
56+
) {
4457
dispatch(
4558
sourcererActions.setSelectedIndexPatterns({
4659
id: SourcererScopeName.timeline,
4760
selectedPatterns: [...ConfigIndexPatterns, signalIndexName],
4861
})
4962
);
5063
}
51-
}, [ConfigIndexPatterns, dispatch, loadingSignalIndex, signalIndexName]);
64+
}, [activeTimeline, ConfigIndexPatterns, dispatch, loadingSignalIndex, signalIndexName]);
5265

5366
// Related to the detection page
5467
useEffect(() => {

x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ export const setSelectedIndexPatterns = actionCreator<{
3434
selectedPatterns: string[];
3535
eventType?: TimelineEventsType;
3636
}>('SET_SELECTED_INDEX_PATTERNS');
37+
38+
export const initTimelineIndexPatterns = actionCreator<{
39+
id: SourcererScopeName;
40+
selectedPatterns: string[];
41+
eventType?: TimelineEventsType;
42+
}>('INIT_TIMELINE_INDEX_PATTERNS');

x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
setSelectedIndexPatterns,
1616
setSignalIndexName,
1717
setSource,
18+
initTimelineIndexPatterns,
1819
} from './actions';
1920
import { initialSourcererState, SourcererModel, SourcererScopeName } from './model';
2021

@@ -76,6 +77,32 @@ export const sourcererReducer = reducerWithInitialState(initialSourcererState)
7677
},
7778
};
7879
})
80+
.case(initTimelineIndexPatterns, (state, { id, selectedPatterns, eventType }) => {
81+
let defaultIndexPatterns = state.configIndexPatterns;
82+
if (isEmpty(selectedPatterns)) {
83+
if (eventType === 'all' && !isEmpty(state.signalIndexName)) {
84+
defaultIndexPatterns = [...state.configIndexPatterns, state.signalIndexName ?? ''];
85+
} else if (eventType === 'raw') {
86+
defaultIndexPatterns = state.configIndexPatterns;
87+
} else if (
88+
!isEmpty(state.signalIndexName) &&
89+
(eventType === 'signal' || eventType === 'alert')
90+
) {
91+
defaultIndexPatterns = [state.signalIndexName ?? ''];
92+
}
93+
}
94+
return {
95+
...state,
96+
sourcererScopes: {
97+
...state.sourcererScopes,
98+
[id]: {
99+
...state.sourcererScopes[id],
100+
selectedPatterns: isEmpty(selectedPatterns) ? defaultIndexPatterns : selectedPatterns,
101+
},
102+
},
103+
};
104+
})
105+
79106
.case(setSource, (state, { id, payload }) => {
80107
const { ...sourcererScopes } = payload;
81108
return {

x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,18 @@ export const getIndexNamesSelectedSelector = () => {
4141
const getScopesSelector = scopesSelector();
4242
const getConfigIndexPatternsSelector = configIndexPatternsSelector();
4343

44-
const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => {
44+
const mapStateToProps = (
45+
state: State,
46+
scopeId: SourcererScopeName
47+
): { indexNames: string[]; previousIndexNames: string } => {
4548
const scope = getScopesSelector(state)[scopeId];
4649
const configIndexPatterns = getConfigIndexPatternsSelector(state);
47-
48-
return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns;
50+
return {
51+
indexNames:
52+
scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns,
53+
previousIndexNames: scope.indexPattern.title,
54+
};
4955
};
50-
5156
return mapStateToProps;
5257
};
5358

x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ describe('alert actions', () => {
4545
searchStrategyClient = {
4646
aggs: {} as ISearchStart['aggs'],
4747
showError: jest.fn(),
48-
search: jest.fn().mockResolvedValue({ data: mockTimelineDetails }),
48+
search: jest
49+
.fn()
50+
.mockImplementation(() => ({ toPromise: () => ({ data: mockTimelineDetails }) })),
4951
searchSource: {} as ISearchStart['searchSource'],
5052
};
5153

@@ -397,6 +399,78 @@ describe('alert actions', () => {
397399
expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps);
398400
});
399401
});
402+
403+
describe('Eql', () => {
404+
test(' with signal.group.id', async () => {
405+
const ecsDataMock: Ecs = {
406+
...mockEcsDataWithAlert,
407+
signal: {
408+
rule: {
409+
...mockEcsDataWithAlert.signal?.rule!,
410+
type: ['eql'],
411+
timeline_id: [''],
412+
},
413+
group: {
414+
id: ['my-group-id'],
415+
},
416+
},
417+
};
418+
419+
await sendAlertToTimelineAction({
420+
createTimeline,
421+
ecsData: ecsDataMock,
422+
nonEcsData: [],
423+
updateTimelineIsLoading,
424+
searchStrategyClient,
425+
});
426+
427+
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
428+
expect(createTimeline).toHaveBeenCalledTimes(1);
429+
expect(createTimeline).toHaveBeenCalledWith({
430+
...defaultTimelineProps,
431+
timeline: {
432+
...defaultTimelineProps.timeline,
433+
dataProviders: [
434+
{
435+
and: [],
436+
enabled: true,
437+
excluded: false,
438+
id:
439+
'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-my-group-id',
440+
kqlQuery: '',
441+
name: '1',
442+
queryMatch: { field: 'signal.group.id', operator: ':', value: 'my-group-id' },
443+
},
444+
],
445+
},
446+
});
447+
});
448+
449+
test(' with NO signal.group.id', async () => {
450+
const ecsDataMock: Ecs = {
451+
...mockEcsDataWithAlert,
452+
signal: {
453+
rule: {
454+
...mockEcsDataWithAlert.signal?.rule!,
455+
type: ['eql'],
456+
timeline_id: [''],
457+
},
458+
},
459+
};
460+
461+
await sendAlertToTimelineAction({
462+
createTimeline,
463+
ecsData: ecsDataMock,
464+
nonEcsData: [],
465+
updateTimelineIsLoading,
466+
searchStrategyClient,
467+
});
468+
469+
expect(updateTimelineIsLoading).not.toHaveBeenCalled();
470+
expect(createTimeline).toHaveBeenCalledTimes(1);
471+
expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps);
472+
});
473+
});
400474
});
401475

402476
describe('determineToAndFrom', () => {

x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,10 @@ export const getThresholdAggregationDataProvider = (
150150
];
151151
};
152152

153-
export const isEqlRule = (ecsData: Ecs) =>
154-
ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'eql';
153+
export const isEqlRuleWithGroupId = (ecsData: Ecs) =>
154+
ecsData.signal?.rule?.type?.length &&
155+
ecsData.signal?.rule?.type[0] === 'eql' &&
156+
ecsData.signal?.group?.id?.length;
155157

156158
export const isThresholdRule = (ecsData: Ecs) =>
157159
ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'threshold';
@@ -181,24 +183,23 @@ export const sendAlertToTimelineAction = async ({
181183
timelineType: TimelineType.template,
182184
},
183185
}),
184-
searchStrategyClient.search<
185-
TimelineEventsDetailsRequestOptions,
186-
TimelineEventsDetailsStrategyResponse
187-
>(
188-
{
189-
defaultIndex: [],
190-
docValueFields: [],
191-
indexName: ecsData._index ?? '',
192-
eventId: ecsData._id,
193-
factoryQueryType: TimelineEventsQueries.details,
194-
},
195-
{
196-
strategy: 'securitySolutionTimelineSearchStrategy',
197-
}
198-
),
186+
searchStrategyClient
187+
.search<TimelineEventsDetailsRequestOptions, TimelineEventsDetailsStrategyResponse>(
188+
{
189+
defaultIndex: [],
190+
docValueFields: [],
191+
indexName: ecsData._index ?? '',
192+
eventId: ecsData._id,
193+
factoryQueryType: TimelineEventsQueries.details,
194+
},
195+
{
196+
strategy: 'securitySolutionTimelineSearchStrategy',
197+
}
198+
)
199+
.toPromise(),
199200
]);
200201
const resultingTimeline: TimelineResult = getOr({}, 'data.getOneTimeline', responseTimeline);
201-
const eventData: TimelineEventsDetailsItem[] = getOr([], 'data', eventDataResp);
202+
const eventData: TimelineEventsDetailsItem[] = eventDataResp.data ?? [];
202203
if (!isEmpty(resultingTimeline)) {
203204
const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline);
204205
const { timeline, notes } = formatTimelineResultToModel(
@@ -327,7 +328,7 @@ export const sendAlertToTimelineAction = async ({
327328
},
328329
},
329330
];
330-
if (isEqlRule(ecsData)) {
331+
if (isEqlRuleWithGroupId(ecsData)) {
331332
const signalGroupId = ecsData.signal?.group?.id?.length
332333
? ecsData.signal?.group?.id[0]
333334
: 'unknown-signal-group-id';

x-pack/plugins/security_solution/public/overview/pages/overview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const OverviewComponent: React.FC<PropsFromRedux> = ({
131131
<EventCounts
132132
filters={filters}
133133
from={from}
134-
indexNames={[]}
134+
indexNames={selectedPatterns}
135135
indexPattern={indexPattern}
136136
query={query}
137137
setQuery={setQuery}

x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli
378378
ruleNote,
379379
}: UpdateTimeline): (() => void) => () => {
380380
dispatch(
381-
sourcererActions.setSelectedIndexPatterns({
381+
sourcererActions.initTimelineIndexPatterns({
382382
id: SourcererScopeName.timeline,
383383
selectedPatterns: timeline.indexNames,
384384
eventType: timeline.eventType,

0 commit comments

Comments
 (0)