Skip to content

Commit

Permalink
[SECURITY] Timeline bug 7.9 (elastic#71748)
Browse files Browse the repository at this point in the history
* remove delay of rendering row

* Fix flyout timeline to behave as we wanted

* Fix tabs on timeline page

* disable sensor visibility when you have less than 100 events in timeline

* Fix container to fit content and not take all the place that it wants

* do not update timeline time when switching top nav

* fix timeline url in case

* review I

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
2 people authored and angorayc committed Jul 15, 2020
1 parent 38e3755 commit e178816
Show file tree
Hide file tree
Showing 27 changed files with 245 additions and 200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui';
import React, { useCallback, useEffect } from 'react';
import styled from 'styled-components';

import { useDispatch } from 'react-redux';
import { CommentRequest } from '../../../../../case/common/api';
import { usePostComment } from '../../containers/use_post_comment';
import { Case } from '../../containers/types';
Expand All @@ -19,12 +18,7 @@ import { Form, useForm, UseField } from '../../../shared_imports';

import * as i18n from './translations';
import { schema } from './schema';
import {
dispatchUpdateTimeline,
queryTimelineById,
} from '../../../timelines/components/open_timeline/helpers';
import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/store/timeline/actions';
import { useApolloClient } from '../../../common/utils/apollo_context';
import { useTimelineClick } from '../utils/use_timeline_click';

const MySpinner = styled(EuiLoadingSpinner)`
position: absolute;
Expand Down Expand Up @@ -53,8 +47,7 @@ export const AddComment = React.memo<AddCommentProps>(
options: { stripEmptyFields: false },
schema,
});
const dispatch = useDispatch();
const apolloClient = useApolloClient();

const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline<CommentRequest>(
form,
'comment'
Expand All @@ -68,30 +61,9 @@ export const AddComment = React.memo<AddCommentProps>(
`${comment}${comment.length > 0 ? '\n\n' : ''}${insertQuote}`
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [insertQuote]);
}, [form, insertQuote]);

const handleTimelineClick = useCallback(
(timelineId: string) => {
queryTimelineById({
apolloClient,
timelineId,
updateIsLoading: ({
id: currentTimelineId,
isLoading: isLoadingTimeline,
}: {
id: string;
isLoading: boolean;
}) =>
dispatch(
dispatchUpdateIsLoading({ id: currentTimelineId, isLoading: isLoadingTimeline })
),
updateTimeline: dispatchUpdateTimeline(dispatch),
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[apolloClient]
);
const handleTimelineClick = useTimelineClick();

const onSubmit = useCallback(async () => {
const { isValid, data } = await form.submit();
Expand All @@ -102,8 +74,8 @@ export const AddComment = React.memo<AddCommentProps>(
postComment(data, onCommentPosted);
form.reset();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [form, onCommentPosted, onCommentSaving]);
}, [form, onCommentPosted, onCommentSaving, postComment]);

return (
<span id="add-comment-permLink">
{isLoading && showLoading && <MySpinner data-test-subj="loading-spinner" size="xl" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@ const useGetCasesMock = useGetCases as jest.Mock;
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
const useUpdateCasesMock = useUpdateCases as jest.Mock;

jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
return {
...originalModule,
useHistory: jest.fn(),
};
});

jest.mock('../../../common/components/link_to');

describe('AllCases', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
EuiBasicTable,
EuiContextMenuPanel,
Expand Down Expand Up @@ -50,6 +49,8 @@ import { ConfigureCaseButton } from '../configure_cases/button';
import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations';
import { LinkButton } from '../../../common/components/links';
import { SecurityPageName } from '../../../app/types';
import { useKibana } from '../../../common/lib/kibana';
import { APP_ID } from '../../../../common/constants';

const Div = styled.div`
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
Expand Down Expand Up @@ -81,13 +82,13 @@ const getSortField = (field: string): SortFieldCase => {
};

interface AllCasesProps {
onRowClick?: (id: string) => void;
onRowClick?: (id?: string) => void;
isModal?: boolean;
userCanCrud: boolean;
}
export const AllCases = React.memo<AllCasesProps>(
({ onRowClick = () => {}, isModal = false, userCanCrud }) => {
const history = useHistory();
({ onRowClick, isModal = false, userCanCrud }) => {
const { navigateToApp } = useKibana().services.application;
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
const { actionLicense } = useGetActionLicense();
const {
Expand Down Expand Up @@ -234,9 +235,15 @@ export const AllCases = React.memo<AllCasesProps>(
const goToCreateCase = useCallback(
(ev) => {
ev.preventDefault();
history.push(getCreateCaseUrl(urlSearch));
if (isModal && onRowClick != null) {
onRowClick();
} else {
navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
path: getCreateCaseUrl(urlSearch),
});
}
},
[history, urlSearch]
[navigateToApp, isModal, onRowClick, urlSearch]
);

const actions = useMemo(
Expand Down Expand Up @@ -445,7 +452,11 @@ export const AllCases = React.memo<AllCasesProps>(
rowProps={(item) =>
isModal
? {
onClick: () => onRowClick(item.id),
onClick: () => {
if (onRowClick != null) {
onRowClick(item.id);
}
},
}
: {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as i18n from './translations';
interface AllCasesModalProps {
onCloseCaseModal: () => void;
showCaseModal: boolean;
onRowClick: (id: string) => void;
onRowClick: (id?: string) => void;
}

export const AllCasesModalComponent = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as i18n from '../../translations';
import { MarkdownEditorForm } from '../../../common/components//markdown_editor/form';
import { useGetTags } from '../../containers/use_get_tags';
import { getCaseDetailsUrl } from '../../../common/components/link_to';
import { useTimelineClick } from '../utils/use_timeline_click';

export const CommonUseField = getUseField({ component: Field });

Expand Down Expand Up @@ -87,15 +88,15 @@ export const Create = React.memo(() => {
form,
'description'
);
const handleTimelineClick = useTimelineClick();

const onSubmit = useCallback(async () => {
const { isValid, data } = await form.submit();
if (isValid) {
// `postCase`'s type is incorrect, it actually returns a promise
await postCase(data);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [form]);
}, [form, postCase]);

const handleSetIsCancel = useCallback(() => {
history.push('/');
Expand Down Expand Up @@ -145,6 +146,7 @@ export const Create = React.memo(() => {
dataTestSubj: 'caseDescription',
idAria: 'caseDescription',
isDisabled: isLoading,
onClickTimeline: handleTimelineClick,
onCursorPositionUpdate: handleCursorChange,
topRightContent: (
<InsertTimelinePopover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('UserActionMarkdown ', () => {

expect(queryTimelineByIdSpy).toBeCalledWith({
apolloClient: mockUseApolloClient(),
graphEventId: '',
timelineId,
updateIsLoading: expect.any(Function),
updateTimeline: expect.any(Function),
Expand All @@ -62,6 +63,7 @@ describe('UserActionMarkdown ', () => {
wrapper.find(`[data-test-subj="markdown-timeline-link"]`).first().simulate('click');
expect(queryTimelineByIdSpy).toBeCalledWith({
apolloClient: mockUseApolloClient(),
graphEventId: '',
timelineId,
updateIsLoading: expect.any(Function),
updateTimeline: expect.any(Function),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/e
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components';

import { useDispatch } from 'react-redux';
import * as i18n from '../case_view/translations';
import { Markdown } from '../../../common/components/markdown';
import { Form, useForm, UseField } from '../../../shared_imports';
import { schema, Content } from './schema';
import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover';
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
import { MarkdownEditorForm } from '../../../common/components//markdown_editor/form';
import {
dispatchUpdateTimeline,
queryTimelineById,
} from '../../../timelines/components/open_timeline/helpers';

import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/store/timeline/actions';
import { useApolloClient } from '../../../common/utils/apollo_context';
import { useTimelineClick } from '../utils/use_timeline_click';

const ContentWrapper = styled.div`
${({ theme }) => css`
Expand All @@ -44,8 +37,6 @@ export const UserActionMarkdown = ({
onChangeEditable,
onSaveContent,
}: UserActionMarkdownProps) => {
const dispatch = useDispatch();
const apolloClient = useApolloClient();
const { form } = useForm<Content>({
defaultValue: { content },
options: { stripEmptyFields: false },
Expand All @@ -59,24 +50,7 @@ export const UserActionMarkdown = ({
onChangeEditable(id);
}, [id, onChangeEditable]);

const handleTimelineClick = useCallback(
(timelineId: string) => {
queryTimelineById({
apolloClient,
timelineId,
updateIsLoading: ({
id: currentTimelineId,
isLoading,
}: {
id: string;
isLoading: boolean;
}) => dispatch(dispatchUpdateIsLoading({ id: currentTimelineId, isLoading })),
updateTimeline: dispatchUpdateTimeline(dispatch),
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[apolloClient]
);
const handleTimelineClick = useTimelineClick();

const handleSaveAction = useCallback(async () => {
const { isValid, data } = await form.submit();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useApolloClient } from '../../../common/utils/apollo_context';
import {
dispatchUpdateTimeline,
queryTimelineById,
} from '../../../timelines/components/open_timeline/helpers';
import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/store/timeline/actions';

export const useTimelineClick = () => {
const dispatch = useDispatch();
const apolloClient = useApolloClient();

const handleTimelineClick = useCallback(
(timelineId: string, graphEventId?: string) => {
queryTimelineById({
apolloClient,
graphEventId,
timelineId,
updateIsLoading: ({
id: currentTimelineId,
isLoading,
}: {
id: string;
isLoading: boolean;
}) => dispatch(dispatchUpdateIsLoading({ id: currentTimelineId, isLoading })),
updateTimeline: dispatchUpdateTimeline(dispatch),
});
},
[apolloClient, dispatch]
);

return handleTimelineClick;
};
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ const EventsViewerComponent: React.FC<Props> = ({

useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isQueryLoading]);
}, [id, isQueryLoading, setIsTimelineLoading]);

const { queryFields, title, unit } = useMemo(() => getManageTimelineById(id), [
getManageTimelineById,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,19 @@ describe('Markdown', () => {
);
wrapper.find('[data-test-subj="markdown-timeline-link"]').first().simulate('click');

expect(onClickTimeline).toHaveBeenCalledWith(timelineId);
expect(onClickTimeline).toHaveBeenCalledWith(timelineId, '');
});

test('timeline link onClick calls onClickTimeline with timelineId and graphEventId', () => {
const graphEventId = '2bc51864784c';
const markdownWithTimelineAndGraphEventLink = `A link to a timeline [timeline](http://localhost:5601/app/siem#/timelines?timeline=(id:'${timelineId}',isOpen:!t,graphEventId:'${graphEventId}'))`;

const wrapper = mount(
<Markdown raw={markdownWithTimelineAndGraphEventLink} onClickTimeline={onClickTimeline} />
);
wrapper.find('[data-test-subj="markdown-timeline-link"]').first().simulate('click');

expect(onClickTimeline).toHaveBeenCalledWith(timelineId, graphEventId);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/* eslint-disable react/display-name */

import { EuiLink, EuiTableRow, EuiTableRowCell, EuiText, EuiToolTip } from '@elastic/eui';
import { clone } from 'lodash/fp';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import styled, { css } from 'styled-components';
Expand Down Expand Up @@ -38,7 +39,7 @@ const REL_NOREFERRER = 'noreferrer';
export const Markdown = React.memo<{
disableLinks?: boolean;
raw?: string;
onClickTimeline?: (timelineId: string) => void;
onClickTimeline?: (timelineId: string, graphEventId?: string) => void;
size?: 'xs' | 's' | 'm';
}>(({ disableLinks = false, onClickTimeline, raw, size = 's' }) => {
const markdownRenderers = {
Expand All @@ -63,11 +64,14 @@ export const Markdown = React.memo<{
),
link: ({ children, href }: { children: React.ReactNode[]; href?: string }) => {
if (onClickTimeline != null && href != null && href.indexOf(`timelines?timeline=(id:`) > -1) {
const timelineId = href.split('timelines?timeline=(id:')[1].split("'")[1] ?? '';
const timelineId = clone(href).split('timeline=(id:')[1].split("'")[1] ?? '';
const graphEventId = href.includes('graphEventId:')
? clone(href).split('graphEventId:')[1].split("'")[1] ?? ''
: '';
return (
<EuiToolTip content={i18n.TIMELINE_ID(timelineId)}>
<EuiLink
onClick={() => onClickTimeline(timelineId)}
onClick={() => onClickTimeline(timelineId, graphEventId)}
data-test-subj="markdown-timeline-link"
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface IMarkdownEditorForm {
field: FieldHook;
idAria: string;
isDisabled: boolean;
onClickTimeline?: (timelineId: string) => void;
onClickTimeline?: (timelineId: string, graphEventId?: string) => void;
onCursorPositionUpdate?: (cursorPosition: CursorPosition) => void;
placeholder?: string;
topRightContent?: React.ReactNode;
Expand Down
Loading

0 comments on commit e178816

Please sign in to comment.