Skip to content

Commit c971531

Browse files
authored
[SIEM] [Cases] Insert timeline and reporters/tags in table bug fixes (#63642) (#63657)
1 parent e8b1317 commit c971531

File tree

9 files changed

+149
-24
lines changed

9 files changed

+149
-24
lines changed

x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const mockLocationWithState = {
2626
state: {
2727
insertTimeline: {
2828
timelineId: 'timeline-id',
29+
timelineSavedObjectId: '34578-3497-5893-47589-34759',
2930
timelineTitle: 'Timeline title',
3031
},
3132
},
@@ -49,7 +50,7 @@ describe('Insert timeline popover ', () => {
4950
payload: { id: 'timeline-id', show: false },
5051
type: 'x-pack/siem/local/timeline/SHOW_TIMELINE',
5152
});
52-
expect(onTimelineChange).toBeCalledWith('Timeline title', 'timeline-id');
53+
expect(onTimelineChange).toBeCalledWith('Timeline title', '34578-3497-5893-47589-34759');
5354
});
5455
it('should do nothing when router state', () => {
5556
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);

x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface InsertTimelinePopoverProps {
2323
interface RouterState {
2424
insertTimeline: {
2525
timelineId: string;
26+
timelineSavedObjectId: string;
2627
timelineTitle: string;
2728
};
2829
}
@@ -46,7 +47,7 @@ export const InsertTimelinePopoverComponent: React.FC<Props> = ({
4647
);
4748
onTimelineChange(
4849
routerState.insertTimeline.timelineTitle,
49-
routerState.insertTimeline.timelineId
50+
routerState.insertTimeline.timelineSavedObjectId
5051
);
5152
setRouterState(null);
5253
}

x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import React, { useCallback } from 'react';
2121
import uuid from 'uuid';
2222
import styled from 'styled-components';
2323
import { useHistory } from 'react-router-dom';
24+
import { useSelector } from 'react-redux';
2425

2526
import { Note } from '../../../lib/note';
2627
import { Notes } from '../../notes';
@@ -29,6 +30,8 @@ import { NOTES_PANEL_WIDTH } from './notes_size';
2930
import { ButtonContainer, DescriptionContainer, LabelText, NameField, StyledStar } from './styles';
3031
import * as i18n from './translations';
3132
import { SiemPageName } from '../../../pages/home/types';
33+
import { timelineSelectors } from '../../../store/timeline';
34+
import { State } from '../../../store';
3235

3336
export const historyToolTip = 'The chronological history of actions related to this timeline';
3437
export const streamLiveToolTip = 'Update the Timeline as new data arrives';
@@ -121,13 +124,17 @@ interface NewCaseProps {
121124

122125
export const NewCase = React.memo<NewCaseProps>(({ onClosePopover, timelineId, timelineTitle }) => {
123126
const history = useHistory();
127+
const { savedObjectId } = useSelector((state: State) =>
128+
timelineSelectors.selectTimeline(state, timelineId)
129+
);
124130
const handleClick = useCallback(() => {
125131
onClosePopover();
126132
history.push({
127133
pathname: `/${SiemPageName.case}/create`,
128134
state: {
129135
insertTimeline: {
130136
timelineId,
137+
timelineSavedObjectId: savedObjectId,
131138
timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE,
132139
},
133140
},

x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ describe('useGetReporters', () => {
6161
});
6262
});
6363

64+
it('refetch reporters', async () => {
65+
const spyOnGetReporters = jest.spyOn(api, 'getReporters');
66+
await act(async () => {
67+
const { result, waitForNextUpdate } = renderHook<string, UseGetReporters>(() =>
68+
useGetReporters()
69+
);
70+
await waitForNextUpdate();
71+
await waitForNextUpdate();
72+
result.current.fetchReporters();
73+
expect(spyOnGetReporters).toHaveBeenCalledTimes(2);
74+
});
75+
});
76+
6477
it('unhappy path', async () => {
6578
const spyOnGetReporters = jest.spyOn(api, 'getReporters');
6679
spyOnGetReporters.mockImplementation(() => {

x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { renderHook, act } from '@testing-library/react-hooks';
8-
import { useGetTags, TagsState } from './use_get_tags';
8+
import { useGetTags, UseGetTags } from './use_get_tags';
99
import { tags } from './mock';
1010
import * as api from './api';
1111

@@ -20,20 +20,21 @@ describe('useGetTags', () => {
2020

2121
it('init', async () => {
2222
await act(async () => {
23-
const { result, waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
23+
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
2424
await waitForNextUpdate();
2525
expect(result.current).toEqual({
2626
tags: [],
2727
isLoading: true,
2828
isError: false,
29+
fetchTags: result.current.fetchTags,
2930
});
3031
});
3132
});
3233

3334
it('calls getTags api', async () => {
3435
const spyOnGetTags = jest.spyOn(api, 'getTags');
3536
await act(async () => {
36-
const { waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
37+
const { waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
3738
await waitForNextUpdate();
3839
await waitForNextUpdate();
3940
expect(spyOnGetTags).toBeCalledWith(abortCtrl.signal);
@@ -42,32 +43,45 @@ describe('useGetTags', () => {
4243

4344
it('fetch tags', async () => {
4445
await act(async () => {
45-
const { result, waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
46+
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
4647
await waitForNextUpdate();
4748
await waitForNextUpdate();
4849
expect(result.current).toEqual({
4950
tags,
5051
isLoading: false,
5152
isError: false,
53+
fetchTags: result.current.fetchTags,
5254
});
5355
});
5456
});
5557

58+
it('refetch tags', async () => {
59+
const spyOnGetTags = jest.spyOn(api, 'getTags');
60+
await act(async () => {
61+
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
62+
await waitForNextUpdate();
63+
await waitForNextUpdate();
64+
result.current.fetchTags();
65+
expect(spyOnGetTags).toHaveBeenCalledTimes(2);
66+
});
67+
});
68+
5669
it('unhappy path', async () => {
5770
const spyOnGetTags = jest.spyOn(api, 'getTags');
5871
spyOnGetTags.mockImplementation(() => {
5972
throw new Error('Something went wrong');
6073
});
6174

6275
await act(async () => {
63-
const { result, waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
76+
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
6477
await waitForNextUpdate();
6578
await waitForNextUpdate();
6679

6780
expect(result.current).toEqual({
6881
tags: [],
6982
isLoading: false,
7083
isError: true,
84+
fetchTags: result.current.fetchTags,
7185
});
7286
});
7387
});

x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ type Action =
2020
| { type: 'FETCH_SUCCESS'; payload: string[] }
2121
| { type: 'FETCH_FAILURE' };
2222

23+
export interface UseGetTags extends TagsState {
24+
fetchTags: () => void;
25+
}
26+
2327
const dataFetchReducer = (state: TagsState, action: Action): TagsState => {
2428
switch (action.type) {
2529
case 'FETCH_INIT':
@@ -47,15 +51,15 @@ const dataFetchReducer = (state: TagsState, action: Action): TagsState => {
4751
};
4852
const initialData: string[] = [];
4953

50-
export const useGetTags = (): TagsState => {
54+
export const useGetTags = (): UseGetTags => {
5155
const [state, dispatch] = useReducer(dataFetchReducer, {
5256
isLoading: true,
5357
isError: false,
5458
tags: initialData,
5559
});
5660
const [, dispatchToaster] = useStateToaster();
5761

58-
useEffect(() => {
62+
const callFetch = () => {
5963
let didCancel = false;
6064
const abortCtrl = new AbortController();
6165

@@ -82,6 +86,9 @@ export const useGetTags = (): TagsState => {
8286
abortCtrl.abort();
8387
didCancel = true;
8488
};
89+
};
90+
useEffect(() => {
91+
callFetch();
8592
}, []);
86-
return state;
93+
return { ...state, fetchTags: callFetch };
8794
};

x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
7+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
88
import {
99
EuiBasicTable,
1010
EuiButton,
@@ -129,13 +129,25 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
129129
id: '',
130130
});
131131
const [deleteBulk, setDeleteBulk] = useState<DeleteCase[]>([]);
132-
133-
const refreshCases = useCallback(() => {
134-
refetchCases();
135-
fetchCasesStatus();
136-
setSelectedCases([]);
137-
setDeleteBulk([]);
138-
}, []);
132+
const filterRefetch = useRef<() => void>();
133+
const setFilterRefetch = useCallback(
134+
(refetchFilter: () => void) => {
135+
filterRefetch.current = refetchFilter;
136+
},
137+
[filterRefetch.current]
138+
);
139+
const refreshCases = useCallback(
140+
(dataRefresh = true) => {
141+
if (dataRefresh) refetchCases();
142+
fetchCasesStatus();
143+
setSelectedCases([]);
144+
setDeleteBulk([]);
145+
if (filterRefetch.current != null) {
146+
filterRefetch.current();
147+
}
148+
},
149+
[filterOptions, queryParams, filterRefetch.current]
150+
);
139151

140152
useEffect(() => {
141153
if (isDeleted) {
@@ -247,6 +259,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
247259
};
248260
}
249261
setQueryParams(newQueryParams);
262+
refreshCases(false);
250263
},
251264
[queryParams]
252265
);
@@ -259,6 +272,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
259272
setQueryParams({ sortField: SortFieldCase.createdAt });
260273
}
261274
setFilters(newFilterOptions);
275+
refreshCases(false);
262276
},
263277
[filterOptions, queryParams]
264278
);
@@ -347,6 +361,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
347361
tags: filterOptions.tags,
348362
status: filterOptions.status,
349363
}}
364+
setFilterRefetch={setFilterRefetch}
350365
/>
351366
{isCasesLoading && isDataEmpty ? (
352367
<Div>

x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ jest.mock('../../../../containers/case/use_get_tags');
1919

2020
const onFilterChanged = jest.fn();
2121
const fetchReporters = jest.fn();
22+
const fetchTags = jest.fn();
23+
const setFilterRefetch = jest.fn();
2224

2325
const props = {
2426
countClosedCases: 1234,
2527
countOpenCases: 99,
2628
onFilterChanged,
2729
initial: DEFAULT_FILTER_OPTIONS,
30+
setFilterRefetch,
2831
};
2932
describe('CasesTableFilters ', () => {
3033
beforeEach(() => {
3134
jest.resetAllMocks();
32-
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'] });
35+
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags });
3336
(useGetReporters as jest.Mock).mockReturnValue({
3437
reporters: ['casetester'],
3538
respReporters: [{ username: 'casetester' }],
@@ -57,7 +60,7 @@ describe('CasesTableFilters ', () => {
5760
.text()
5861
).toEqual('Closed cases (1234)');
5962
});
60-
it('should call onFilterChange when tags change', () => {
63+
it('should call onFilterChange when selected tags change', () => {
6164
const wrapper = mount(
6265
<TestProviders>
6366
<CasesTableFilters {...props} />
@@ -74,7 +77,7 @@ describe('CasesTableFilters ', () => {
7477

7578
expect(onFilterChanged).toBeCalledWith({ tags: ['coke'] });
7679
});
77-
it('should call onFilterChange when reporters change', () => {
80+
it('should call onFilterChange when selected reporters change', () => {
7881
const wrapper = mount(
7982
<TestProviders>
8083
<CasesTableFilters {...props} />
@@ -118,4 +121,45 @@ describe('CasesTableFilters ', () => {
118121

119122
expect(onFilterChanged).toBeCalledWith({ status: 'closed' });
120123
});
124+
it('should call on load setFilterRefetch', () => {
125+
mount(
126+
<TestProviders>
127+
<CasesTableFilters {...props} />
128+
</TestProviders>
129+
);
130+
expect(setFilterRefetch).toHaveBeenCalled();
131+
});
132+
it('should remove tag from selected tags when tag no longer exists', () => {
133+
const ourProps = {
134+
...props,
135+
initial: {
136+
...DEFAULT_FILTER_OPTIONS,
137+
tags: ['pepsi', 'rc'],
138+
},
139+
};
140+
mount(
141+
<TestProviders>
142+
<CasesTableFilters {...ourProps} />
143+
</TestProviders>
144+
);
145+
expect(onFilterChanged).toHaveBeenCalledWith({ tags: ['pepsi'] });
146+
});
147+
it('should remove reporter from selected reporters when reporter no longer exists', () => {
148+
const ourProps = {
149+
...props,
150+
initial: {
151+
...DEFAULT_FILTER_OPTIONS,
152+
reporters: [
153+
{ username: 'casetester', full_name: null, email: null },
154+
{ username: 'batman', full_name: null, email: null },
155+
],
156+
},
157+
};
158+
mount(
159+
<TestProviders>
160+
<CasesTableFilters {...ourProps} />
161+
</TestProviders>
162+
);
163+
expect(onFilterChanged).toHaveBeenCalledWith({ reporters: [{ username: 'casetester' }] });
164+
});
121165
});

0 commit comments

Comments
 (0)