Skip to content

Commit

Permalink
[SIEM] [Cases] Insert timeline and reporters/tags in table bug fixes (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic authored Apr 16, 2020
1 parent 3d41ca6 commit 31ed266
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const mockLocationWithState = {
state: {
insertTimeline: {
timelineId: 'timeline-id',
timelineSavedObjectId: '34578-3497-5893-47589-34759',
timelineTitle: 'Timeline title',
},
},
Expand All @@ -49,7 +50,7 @@ describe('Insert timeline popover ', () => {
payload: { id: 'timeline-id', show: false },
type: 'x-pack/siem/local/timeline/SHOW_TIMELINE',
});
expect(onTimelineChange).toBeCalledWith('Timeline title', 'timeline-id');
expect(onTimelineChange).toBeCalledWith('Timeline title', '34578-3497-5893-47589-34759');
});
it('should do nothing when router state', () => {
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface InsertTimelinePopoverProps {
interface RouterState {
insertTimeline: {
timelineId: string;
timelineSavedObjectId: string;
timelineTitle: string;
};
}
Expand All @@ -46,7 +47,7 @@ export const InsertTimelinePopoverComponent: React.FC<Props> = ({
);
onTimelineChange(
routerState.insertTimeline.timelineTitle,
routerState.insertTimeline.timelineId
routerState.insertTimeline.timelineSavedObjectId
);
setRouterState(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import React, { useCallback } from 'react';
import uuid from 'uuid';
import styled from 'styled-components';
import { useHistory } from 'react-router-dom';
import { useSelector } from 'react-redux';

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

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

export const NewCase = React.memo<NewCaseProps>(({ onClosePopover, timelineId, timelineTitle }) => {
const history = useHistory();
const { savedObjectId } = useSelector((state: State) =>
timelineSelectors.selectTimeline(state, timelineId)
);
const handleClick = useCallback(() => {
onClosePopover();
history.push({
pathname: `/${SiemPageName.case}/create`,
state: {
insertTimeline: {
timelineId,
timelineSavedObjectId: savedObjectId,
timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ describe('useGetReporters', () => {
});
});

it('refetch reporters', async () => {
const spyOnGetReporters = jest.spyOn(api, 'getReporters');
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseGetReporters>(() =>
useGetReporters()
);
await waitForNextUpdate();
await waitForNextUpdate();
result.current.fetchReporters();
expect(spyOnGetReporters).toHaveBeenCalledTimes(2);
});
});

it('unhappy path', async () => {
const spyOnGetReporters = jest.spyOn(api, 'getReporters');
spyOnGetReporters.mockImplementation(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

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

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

it('init', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
await waitForNextUpdate();
expect(result.current).toEqual({
tags: [],
isLoading: true,
isError: false,
fetchTags: result.current.fetchTags,
});
});
});

it('calls getTags api', async () => {
const spyOnGetTags = jest.spyOn(api, 'getTags');
await act(async () => {
const { waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
const { waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
await waitForNextUpdate();
await waitForNextUpdate();
expect(spyOnGetTags).toBeCalledWith(abortCtrl.signal);
Expand All @@ -42,32 +43,45 @@ describe('useGetTags', () => {

it('fetch tags', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
await waitForNextUpdate();
await waitForNextUpdate();
expect(result.current).toEqual({
tags,
isLoading: false,
isError: false,
fetchTags: result.current.fetchTags,
});
});
});

it('refetch tags', async () => {
const spyOnGetTags = jest.spyOn(api, 'getTags');
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
await waitForNextUpdate();
await waitForNextUpdate();
result.current.fetchTags();
expect(spyOnGetTags).toHaveBeenCalledTimes(2);
});
});

it('unhappy path', async () => {
const spyOnGetTags = jest.spyOn(api, 'getTags');
spyOnGetTags.mockImplementation(() => {
throw new Error('Something went wrong');
});

await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, TagsState>(() => useGetTags());
const { result, waitForNextUpdate } = renderHook<string, UseGetTags>(() => useGetTags());
await waitForNextUpdate();
await waitForNextUpdate();

expect(result.current).toEqual({
tags: [],
isLoading: false,
isError: true,
fetchTags: result.current.fetchTags,
});
});
});
Expand Down
13 changes: 10 additions & 3 deletions x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type Action =
| { type: 'FETCH_SUCCESS'; payload: string[] }
| { type: 'FETCH_FAILURE' };

export interface UseGetTags extends TagsState {
fetchTags: () => void;
}

const dataFetchReducer = (state: TagsState, action: Action): TagsState => {
switch (action.type) {
case 'FETCH_INIT':
Expand Down Expand Up @@ -47,15 +51,15 @@ const dataFetchReducer = (state: TagsState, action: Action): TagsState => {
};
const initialData: string[] = [];

export const useGetTags = (): TagsState => {
export const useGetTags = (): UseGetTags => {
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: true,
isError: false,
tags: initialData,
});
const [, dispatchToaster] = useStateToaster();

useEffect(() => {
const callFetch = () => {
let didCancel = false;
const abortCtrl = new AbortController();

Expand All @@ -82,6 +86,9 @@ export const useGetTags = (): TagsState => {
abortCtrl.abort();
didCancel = true;
};
};
useEffect(() => {
callFetch();
}, []);
return state;
return { ...state, fetchTags: callFetch };
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
EuiBasicTable,
EuiButton,
Expand Down Expand Up @@ -129,13 +129,25 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
id: '',
});
const [deleteBulk, setDeleteBulk] = useState<DeleteCase[]>([]);

const refreshCases = useCallback(() => {
refetchCases();
fetchCasesStatus();
setSelectedCases([]);
setDeleteBulk([]);
}, []);
const filterRefetch = useRef<() => void>();
const setFilterRefetch = useCallback(
(refetchFilter: () => void) => {
filterRefetch.current = refetchFilter;
},
[filterRefetch.current]
);
const refreshCases = useCallback(
(dataRefresh = true) => {
if (dataRefresh) refetchCases();
fetchCasesStatus();
setSelectedCases([]);
setDeleteBulk([]);
if (filterRefetch.current != null) {
filterRefetch.current();
}
},
[filterOptions, queryParams, filterRefetch.current]
);

useEffect(() => {
if (isDeleted) {
Expand Down Expand Up @@ -247,6 +259,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
};
}
setQueryParams(newQueryParams);
refreshCases(false);
},
[queryParams]
);
Expand All @@ -259,6 +272,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
setQueryParams({ sortField: SortFieldCase.createdAt });
}
setFilters(newFilterOptions);
refreshCases(false);
},
[filterOptions, queryParams]
);
Expand Down Expand Up @@ -347,6 +361,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
tags: filterOptions.tags,
status: filterOptions.status,
}}
setFilterRefetch={setFilterRefetch}
/>
{isCasesLoading && isDataEmpty ? (
<Div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ jest.mock('../../../../containers/case/use_get_tags');

const onFilterChanged = jest.fn();
const fetchReporters = jest.fn();
const fetchTags = jest.fn();
const setFilterRefetch = jest.fn();

const props = {
countClosedCases: 1234,
countOpenCases: 99,
onFilterChanged,
initial: DEFAULT_FILTER_OPTIONS,
setFilterRefetch,
};
describe('CasesTableFilters ', () => {
beforeEach(() => {
jest.resetAllMocks();
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'] });
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags });
(useGetReporters as jest.Mock).mockReturnValue({
reporters: ['casetester'],
respReporters: [{ username: 'casetester' }],
Expand Down Expand Up @@ -57,7 +60,7 @@ describe('CasesTableFilters ', () => {
.text()
).toEqual('Closed cases (1234)');
});
it('should call onFilterChange when tags change', () => {
it('should call onFilterChange when selected tags change', () => {
const wrapper = mount(
<TestProviders>
<CasesTableFilters {...props} />
Expand All @@ -74,7 +77,7 @@ describe('CasesTableFilters ', () => {

expect(onFilterChanged).toBeCalledWith({ tags: ['coke'] });
});
it('should call onFilterChange when reporters change', () => {
it('should call onFilterChange when selected reporters change', () => {
const wrapper = mount(
<TestProviders>
<CasesTableFilters {...props} />
Expand Down Expand Up @@ -118,4 +121,45 @@ describe('CasesTableFilters ', () => {

expect(onFilterChanged).toBeCalledWith({ status: 'closed' });
});
it('should call on load setFilterRefetch', () => {
mount(
<TestProviders>
<CasesTableFilters {...props} />
</TestProviders>
);
expect(setFilterRefetch).toHaveBeenCalled();
});
it('should remove tag from selected tags when tag no longer exists', () => {
const ourProps = {
...props,
initial: {
...DEFAULT_FILTER_OPTIONS,
tags: ['pepsi', 'rc'],
},
};
mount(
<TestProviders>
<CasesTableFilters {...ourProps} />
</TestProviders>
);
expect(onFilterChanged).toHaveBeenCalledWith({ tags: ['pepsi'] });
});
it('should remove reporter from selected reporters when reporter no longer exists', () => {
const ourProps = {
...props,
initial: {
...DEFAULT_FILTER_OPTIONS,
reporters: [
{ username: 'casetester', full_name: null, email: null },
{ username: 'batman', full_name: null, email: null },
],
},
};
mount(
<TestProviders>
<CasesTableFilters {...ourProps} />
</TestProviders>
);
expect(onFilterChanged).toHaveBeenCalledWith({ reporters: [{ username: 'casetester' }] });
});
});
Loading

0 comments on commit 31ed266

Please sign in to comment.