Remove all cases related code from timelines (#127003)

This commit is contained in:
Esteban Beltran 2022-03-08 09:01:09 +01:00 committed by GitHub
parent fc3aedcf78
commit 5ad355e8c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2 additions and 1420 deletions

View file

@ -15,18 +15,4 @@ export const mockTimelines = {
onBlur: jest.fn(),
onKeyDown: jest.fn(),
}),
getAddToCasePopover: jest
.fn()
.mockReturnValue(<div data-test-subj="add-to-case-action">{'Add to case'}</div>),
getAddToCaseAction: jest.fn(),
getAddToExistingCaseButton: jest.fn().mockReturnValue(
<div key="add-to-existing-case-action" data-test-subj="add-to-existing-case-action">
{'Add to existing case'}
</div>
),
getAddToNewCaseButton: jest.fn().mockReturnValue(
<div key="add-to-new-case-action" data-test-subj="add-to-new-case-action">
{'Add to new case'}
</div>
),
};

View file

@ -52,17 +52,6 @@ jest.mock('../../../../../common/lib/kibana', () => ({
useGetUserCasesPermissions: jest.fn(),
}));
jest.mock(
'../../../../../../../timelines/public/components/actions/timeline/cases/add_to_case_action',
() => {
return {
AddToCasePopover: () => {
return <div data-test-subj="add-to-case-action">{'Add to case'}</div>;
},
};
}
);
describe('EventColumnView', () => {
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
(useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default);

View file

@ -61,10 +61,6 @@ jest.mock('../../../../common/lib/kibana', () => {
onBlur: jest.fn(),
onKeyDown: jest.fn(),
}),
getAddToCasePopover: jest
.fn()
.mockReturnValue(<div data-test-subj="add-to-case-action">{'Add to case'}</div>),
getAddToCaseAction: jest.fn(),
},
},
}),

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './timeline';

View file

@ -1,195 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { mount } from 'enzyme';
import {
TestProviders,
mockGetAllCasesSelectorModal,
mockGetCreateCaseFlyout,
} from '../../../../mock';
import { AddToCaseAction } from './add_to_case_action';
import { SECURITY_SOLUTION_OWNER } from '../../../../../../cases/common';
import { AddToCaseActionButton } from './add_to_case_action_button';
import { ALERT_RULE_UUID } from '@kbn/rule-data-utils';
jest.mock('react-router-dom', () => ({
useLocation: () => ({
search: '',
}),
}));
jest.mock('./helpers');
describe('AddToCaseAction', () => {
const props = {
event: {
_id: 'test-id',
data: [],
ecs: {
_id: 'test-id',
_index: 'test-index',
signal: { rule: { id: ['rule-id'], name: ['rule-name'], false_positives: [] } },
},
},
casePermissions: {
crud: true,
read: true,
},
appId: 'securitySolutionUI',
owner: 'securitySolution',
onClose: () => null,
};
beforeEach(() => {
jest.clearAllMocks();
});
it('it renders', () => {
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton {...props} />
<AddToCaseAction {...props} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).exists()).toBeTruthy();
});
it('it opens the context menu', () => {
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton {...props} />
<AddToCaseAction {...props} />
</TestProviders>
);
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
expect(wrapper.find(`[data-test-subj="add-new-case-item"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).exists()).toBeTruthy();
});
it('it opens the create case flyout', () => {
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton {...props} />
<AddToCaseAction {...props} />
</TestProviders>
);
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click');
expect(mockGetCreateCaseFlyout).toHaveBeenCalled();
});
it('it opens the all cases modal', () => {
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton {...props} />
<AddToCaseAction {...props} />
</TestProviders>
);
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click');
expect(mockGetAllCasesSelectorModal).toHaveBeenCalled();
});
it('it set rule information as null when missing', () => {
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton
{...props}
event={{
_id: 'test-id',
data: [{ field: ALERT_RULE_UUID, value: ['rule-id'] }],
ecs: {
_id: 'test-id',
_index: 'test-index',
signal: { rule: { id: ['rule-id'], false_positives: [] } },
},
}}
/>
<AddToCaseAction
{...props}
event={{
_id: 'test-id',
data: [{ field: ALERT_RULE_UUID, value: ['rule-id'] }],
ecs: {
_id: 'test-id',
_index: 'test-index',
signal: { rule: { id: ['rule-id'], false_positives: [] } },
},
}}
/>
</TestProviders>
);
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click');
expect(mockGetAllCasesSelectorModal.mock.calls[0][0].alertData).toEqual({
alertId: 'test-id',
index: 'test-index',
rule: {
id: 'rule-id',
name: null,
},
owner: SECURITY_SOLUTION_OWNER,
});
});
it('disabled when event type is not supported', () => {
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton
{...props}
event={{
_id: 'test-id',
data: [],
ecs: {
_id: 'test-id',
_index: 'test-index',
},
}}
/>
<AddToCaseAction
{...props}
event={{
_id: 'test-id',
data: [],
ecs: {
_id: 'test-id',
_index: 'test-index',
},
}}
/>
</TestProviders>
);
expect(
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().prop('isDisabled')
).toBeTruthy();
});
it('hides the icon when user does not have crud permissions', () => {
const newProps = {
...props,
casePermissions: {
crud: false,
read: true,
},
};
const wrapper = mount(
<TestProviders>
<AddToCaseActionButton {...newProps} />
<AddToCaseAction {...newProps} />
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).exists()).toBeFalsy();
});
});

View file

@ -1,147 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo, useMemo, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import {
GetAllCasesSelectorModalProps,
GetCreateCaseFlyoutProps,
} from '../../../../../../cases/public';
import {
CaseStatuses,
StatusAll,
CasesFeatures,
CommentType,
} from '../../../../../../cases/common';
import { TimelineItem } from '../../../../../common/search_strategy';
import { useAddToCase, normalizedEventFields } from '../../../../hooks/use_add_to_case';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimelinesStartServices } from '../../../../types';
import { setOpenAddToExistingCase, setOpenAddToNewCase } from '../../../../store/t_grid/actions';
export interface AddToCaseActionProps {
event?: TimelineItem;
useInsertTimeline?: Function;
casePermissions: {
crud: boolean;
read: boolean;
} | null;
appId: string;
owner: string;
onClose?: Function;
casesFeatures?: CasesFeatures;
}
const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
event,
useInsertTimeline,
casePermissions,
appId,
owner,
onClose,
casesFeatures,
}) => {
const eventId = event?.ecs._id ?? '';
const eventIndex = event?.ecs._index ?? '';
const dispatch = useDispatch();
const { cases } = useKibana<TimelinesStartServices>().services;
const {
onCaseClicked,
onCaseSuccess,
onCaseCreated,
isAllCaseModalOpen,
isCreateCaseFlyoutOpen,
} = useAddToCase({ event, casePermissions, appId, owner, onClose });
const allCasesSelectorModalProps: GetAllCasesSelectorModalProps = useMemo(() => {
const { ruleId, ruleName } = normalizedEventFields(event);
return {
alertData: {
alertId: eventId,
index: eventIndex ?? '',
rule: {
id: ruleId,
name: ruleName,
},
owner,
},
hooks: {
useInsertTimeline,
},
hiddenStatuses: [CaseStatuses.closed, StatusAll],
onRowClick: onCaseClicked,
updateCase: onCaseSuccess,
userCanCrud: casePermissions?.crud ?? false,
owner: [owner],
onClose: () => dispatch(setOpenAddToExistingCase({ id: eventId, isOpen: false })),
};
}, [
casePermissions?.crud,
onCaseSuccess,
onCaseClicked,
eventId,
eventIndex,
dispatch,
owner,
useInsertTimeline,
event,
]);
const closeCaseFlyoutOpen = useCallback(() => {
dispatch(setOpenAddToNewCase({ id: eventId, isOpen: false }));
}, [dispatch, eventId]);
const createCaseFlyoutProps: GetCreateCaseFlyoutProps = useMemo(() => {
const { ruleId, ruleName } = normalizedEventFields(event);
const attachments = [
{
alertId: eventId,
index: eventIndex ?? '',
rule: {
id: ruleId,
name: ruleName,
},
owner,
type: CommentType.alert as const,
},
];
return {
afterCaseCreated: onCaseCreated,
onClose: closeCaseFlyoutOpen,
onSuccess: onCaseSuccess,
useInsertTimeline,
owner: [owner],
userCanCrud: casePermissions?.crud ?? false,
features: casesFeatures,
attachments,
};
}, [
event,
eventId,
eventIndex,
owner,
onCaseCreated,
closeCaseFlyoutOpen,
onCaseSuccess,
useInsertTimeline,
casePermissions?.crud,
casesFeatures,
]);
return (
<>
{isCreateCaseFlyoutOpen && cases.getCreateCaseFlyout(createCaseFlyoutProps)}
{isAllCaseModalOpen && cases.getAllCasesSelectorModal(allCasesSelectorModalProps)}
</>
);
};
AddToCaseActionComponent.displayName = 'AddToCaseAction';
export const AddToCaseAction = memo(AddToCaseActionComponent);
// eslint-disable-next-line import/no-default-export
export default AddToCaseAction;

View file

@ -1,109 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo, useMemo } from 'react';
import {
EuiPopover,
EuiButtonIcon,
EuiContextMenuPanel,
EuiText,
EuiContextMenuItem,
EuiToolTip,
} from '@elastic/eui';
import { AddToCaseActionProps } from './add_to_case_action';
import { useAddToCase } from '../../../../hooks/use_add_to_case';
import { ActionIconItem } from '../../action_icon_item';
import * as i18n from './translations';
const AddToCaseActionButtonComponent: React.FC<AddToCaseActionProps> = ({
event,
useInsertTimeline,
casePermissions,
appId,
owner,
onClose,
}) => {
const {
addNewCaseClick,
addExistingCaseClick,
isDisabled,
userCanCrud,
isEventSupported,
openPopover,
closePopover,
isPopoverOpen,
} = useAddToCase({ event, useInsertTimeline, casePermissions, appId, owner, onClose });
const tooltipContext = userCanCrud
? isEventSupported
? i18n.ACTION_ADD_TO_CASE_TOOLTIP
: i18n.UNSUPPORTED_EVENTS_MSG
: i18n.PERMISSIONS_MSG;
const items = useMemo(
() => [
<EuiContextMenuItem
key="add-new-case-menu-item"
onClick={addNewCaseClick}
aria-label={i18n.ACTION_ADD_NEW_CASE}
data-test-subj="add-new-case-item"
disabled={isDisabled}
>
<EuiText size="m">{i18n.ACTION_ADD_NEW_CASE}</EuiText>
</EuiContextMenuItem>,
<EuiContextMenuItem
key="add-existing-case-menu-item"
onClick={addExistingCaseClick}
aria-label={i18n.ACTION_ADD_EXISTING_CASE}
data-test-subj="add-existing-case-menu-item"
disabled={isDisabled}
>
<EuiText size="m">{i18n.ACTION_ADD_EXISTING_CASE}</EuiText>
</EuiContextMenuItem>,
],
[addExistingCaseClick, addNewCaseClick, isDisabled]
);
const button = useMemo(
() => (
<EuiToolTip data-test-subj="attach-alert-to-case-tooltip" content={tooltipContext}>
<EuiButtonIcon
data-test-subj="attach-alert-to-case-button"
size="s"
iconType="folderClosed"
onClick={openPopover}
isDisabled={isDisabled}
aria-label={tooltipContext}
/>
</EuiToolTip>
),
[isDisabled, openPopover, tooltipContext]
);
return (
<>
{userCanCrud && (
<ActionIconItem>
<EuiPopover
id="attachAlertToCasePanel"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
repositionOnScroll
>
<EuiContextMenuPanel items={items} />
</EuiPopover>
</ActionIconItem>
)}
</>
);
};
export const AddToCaseActionButton = memo(AddToCaseActionButtonComponent);
// eslint-disable-next-line import/no-default-export
export default AddToCaseActionButton;

View file

@ -1,76 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo } from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimelinesStartServices } from '../../../../types';
import { useAddToCase } from '../../../../hooks/use_add_to_case';
import { AddToCaseActionProps } from './add_to_case_action';
import * as i18n from './translations';
interface AddToCaseActionButtonProps extends AddToCaseActionProps {
ariaLabel?: string;
}
const AddToCaseActionButtonComponent: React.FC<AddToCaseActionButtonProps> = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
event,
useInsertTimeline,
casePermissions,
appId,
owner,
onClose,
}) => {
const { onCaseSuccess, onCaseClicked, isDisabled, userCanCrud, caseAttachments } = useAddToCase({
event,
useInsertTimeline,
casePermissions,
appId,
owner,
onClose,
});
const { cases } = useKibana<TimelinesStartServices>().services;
const addToCaseModal = cases.hooks.getUseCasesAddToExistingCaseModal({
attachments: caseAttachments,
updateCase: onCaseSuccess,
onRowClick: onCaseClicked,
});
// TODO To be further refactored and moved to cases plugins
// https://github.com/elastic/kibana/issues/123183
const handleClick = () => {
// close the popover
if (onClose) {
onClose();
}
addToCaseModal.open();
};
return (
<>
{userCanCrud && (
<EuiContextMenuItem
aria-label={ariaLabel}
data-test-subj="add-existing-case-menu-item"
onClick={handleClick}
// needs forced size="s" since it is lazy loaded and the EuiContextMenuPanel can not initialize the size
size="s"
disabled={isDisabled}
>
{i18n.ACTION_ADD_EXISTING_CASE}
</EuiContextMenuItem>
)}
</>
);
};
export const AddToExistingCaseButton = memo(AddToCaseActionButtonComponent);
// eslint-disable-next-line import/no-default-export
export default AddToExistingCaseButton;

View file

@ -1,77 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo } from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimelinesStartServices } from '../../../../types';
import { useAddToCase } from '../../../../hooks/use_add_to_case';
import { AddToCaseActionProps } from './add_to_case_action';
import * as i18n from './translations';
export interface AddToNewCaseButtonProps extends AddToCaseActionProps {
ariaLabel?: string;
}
const AddToNewCaseButtonComponent: React.FC<AddToNewCaseButtonProps> = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
event,
useInsertTimeline,
casePermissions,
appId,
owner,
onClose,
}) => {
const { isDisabled, userCanCrud, caseAttachments, onCaseSuccess, onCaseCreated } = useAddToCase({
event,
useInsertTimeline,
casePermissions,
appId,
owner,
onClose,
});
const { cases } = useKibana<TimelinesStartServices>().services;
const createCaseFlyout = cases.hooks.getUseCasesAddToNewCaseFlyout({
attachments: caseAttachments,
afterCaseCreated: onCaseCreated,
onSuccess: onCaseSuccess,
});
// TODO To be further refactored and moved to cases plugins
// https://github.com/elastic/kibana/issues/123183
const handleClick = () => {
// close the popover
if (onClose) {
onClose();
}
createCaseFlyout.open();
};
return (
<>
{userCanCrud && (
<EuiContextMenuItem
aria-label={ariaLabel}
data-test-subj="add-new-case-item"
onClick={handleClick}
// needs forced size="s" since it is lazy loaded and the EuiContextMenuPanel can not initialize the size
size="s"
disabled={isDisabled}
>
{i18n.ACTION_ADD_NEW_CASE}
</EuiContextMenuItem>
)}
</>
);
};
AddToNewCaseButtonComponent.displayName = 'AddToNewCaseButton';
export const AddToNewCaseButton = memo(AddToNewCaseButtonComponent);
// eslint-disable-next-line import/no-default-export
export default AddToNewCaseButton;

View file

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import 'jest-styled-components';
import type { MockedKeys } from '@kbn/utility-types/jest';
import { CoreStart } from 'kibana/public';
import { coreMock } from 'src/core/public/mocks';
import type { IToasts } from '../../../../../../../../src/core/public';
import { createUpdateSuccessToaster } from './helpers';
import { Case } from '../../../../../../cases/common';
let mockCoreStart: MockedKeys<CoreStart>;
let toasts: IToasts;
let toastsSpy: jest.SpyInstance;
const theCase = {
id: 'case-id',
title: 'My case',
settings: {
syncAlerts: true,
},
} as Case;
describe('helpers', () => {
beforeEach(() => {
mockCoreStart = coreMock.createStart();
});
describe('createUpdateSuccessToaster', () => {
it('creates the correct toast when the sync alerts is on', () => {
const onViewCaseClick = jest.fn();
toasts = mockCoreStart.notifications.toasts;
toastsSpy = jest.spyOn(mockCoreStart.notifications.toasts, 'addSuccess');
createUpdateSuccessToaster(toasts, theCase, onViewCaseClick);
expect(toastsSpy).toHaveBeenCalled();
});
});
});

View file

@ -1,43 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import styled from 'styled-components';
import { ToasterContent } from './toaster_content';
import * as i18n from './translations';
import type { Case } from '../../../../../../cases/common';
import type { ToastsStart, Toast } from '../../../../../../../../src/core/public';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
const LINE_CLAMP = 3;
const Title = styled.span`
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: ${LINE_CLAMP};
-webkit-box-orient: vertical;
overflow: hidden;
`;
export const createUpdateSuccessToaster = (
toasts: ToastsStart,
theCase: Case,
onViewCaseClick: (id: string) => void
): Toast => {
return toasts.addSuccess({
color: 'success',
iconType: 'check',
title: toMountPoint(<Title>{i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title)}</Title>),
text: toMountPoint(
<ToasterContent
caseId={theCase.id}
syncAlerts={theCase.settings.syncAlerts}
onViewCaseClick={onViewCaseClick}
/>
),
});
};

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './add_to_case_action';
export * from './toaster_content';
export * from './add_to_existing_case_button';
export * from './add_to_new_case_button';

View file

@ -1,46 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { mount } from 'enzyme';
import { ToasterContent } from './toaster_content';
describe('ToasterContent', () => {
const onViewCaseClick = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
it('renders with syncAlerts=true', () => {
const wrapper = mount(
<ToasterContent caseId="case-id" syncAlerts={true} onViewCaseClick={onViewCaseClick} />
);
expect(wrapper.find('[data-test-subj="toaster-content-case-view-link"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="toaster-content-sync-text"]').exists()).toBeTruthy();
});
it('renders with syncAlerts=false', () => {
const wrapper = mount(
<ToasterContent caseId="case-id" syncAlerts={false} onViewCaseClick={onViewCaseClick} />
);
expect(wrapper.find('[data-test-subj="toaster-content-case-view-link"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="toaster-content-sync-text"]').exists()).toBeFalsy();
});
it('calls onViewCaseClick', () => {
const wrapper = mount(
<ToasterContent caseId="case-id" syncAlerts={false} onViewCaseClick={onViewCaseClick} />
);
wrapper.find('[data-test-subj="toaster-content-case-view-link"]').first().simulate('click');
expect(onViewCaseClick).toHaveBeenCalled();
});
});

View file

@ -1,47 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo, useCallback } from 'react';
import { EuiButtonEmpty, EuiText } from '@elastic/eui';
import styled from 'styled-components';
import * as i18n from './translations';
const EuiTextStyled = styled(EuiText)`
${({ theme }) => `
margin-bottom: ${theme.eui?.paddingSizes?.s ?? 8}px;
`}
`;
interface Props {
caseId: string;
syncAlerts: boolean;
onViewCaseClick: (id: string) => void;
}
const ToasterContentComponent: React.FC<Props> = ({ caseId, syncAlerts, onViewCaseClick }) => {
const onClick = useCallback(() => onViewCaseClick(caseId), [caseId, onViewCaseClick]);
return (
<>
{syncAlerts && (
<EuiTextStyled size="s" data-test-subj="toaster-content-sync-text">
{i18n.CASE_CREATED_SUCCESS_TOAST_TEXT}
</EuiTextStyled>
)}
<EuiButtonEmpty
size="xs"
flush="left"
onClick={onClick}
data-test-subj="toaster-content-case-view-link"
>
{i18n.VIEW_CASE}
</EuiButtonEmpty>
</>
);
};
export const ToasterContent = memo(ToasterContentComponent);

View file

@ -1,75 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const ACTION_ADD_CASE = i18n.translate('xpack.timelines.cases.timeline.actions.addCase', {
defaultMessage: 'Add to case',
});
export const ACTION_ADD_NEW_CASE = i18n.translate(
'xpack.timelines.cases.timeline.actions.addNewCase',
{
defaultMessage: 'Add to new case',
}
);
export const ACTION_ADD_EXISTING_CASE = i18n.translate(
'xpack.timelines.cases.timeline.actions.addExistingCase',
{
defaultMessage: 'Add to existing case',
}
);
export const ACTION_ADD_TO_CASE_ARIA_LABEL = i18n.translate(
'xpack.timelines.cases.timeline.actions.addToCaseAriaLabel',
{
defaultMessage: 'Attach alert to case',
}
);
export const ACTION_ADD_TO_CASE_TOOLTIP = i18n.translate(
'xpack.timelines.cases.timeline.actions.addToCaseTooltip',
{
defaultMessage: 'Add to case',
}
);
export const CASE_CREATED_SUCCESS_TOAST = (title: string) =>
i18n.translate('xpack.timelines.cases.timeline.actions.caseCreatedSuccessToast', {
values: { title },
defaultMessage: 'An alert has been added to "{title}"',
});
export const CASE_CREATED_SUCCESS_TOAST_TEXT = i18n.translate(
'xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastText',
{
defaultMessage: 'Alerts in this case have their status synched with the case status',
}
);
export const VIEW_CASE = i18n.translate(
'xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink',
{
defaultMessage: 'View Case',
}
);
export const PERMISSIONS_MSG = i18n.translate(
'xpack.timelines.cases.timeline.actions.permissionsMessage',
{
defaultMessage:
'You are currently missing the required permissions to attach alerts to cases. Please contact your administrator for further assistance.',
}
);
export const UNSUPPORTED_EVENTS_MSG = i18n.translate(
'xpack.timelines.cases.timeline.actions.unsupportedEventsMessage',
{
defaultMessage: 'This event cannot be attached to a case',
}
);

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './cases';

View file

@ -8,7 +8,7 @@ import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React, { useEffect, useMemo, useState, useRef } from 'react';
import styled from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Filter, Query } from '@kbn/es-query';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
@ -39,7 +39,6 @@ import { LastUpdatedAt } from '../..';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexItem, UpdatedFlexGroup } from '../styles';
import { InspectButton, InspectButtonContainer } from '../../inspect';
import { useFetchIndex } from '../../../container/source';
import { AddToCaseAction } from '../../actions/timeline/cases/add_to_case_action';
import { TGridLoading, TGridEmpty, TimelineContext } from '../shared';
const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible: boolean }>`
@ -76,16 +75,7 @@ const ScrollableFlexItem = styled(EuiFlexItem)`
overflow: auto;
`;
const casesFeatures = { alerts: { sync: false } };
export interface TGridStandaloneProps {
appId: string;
casesOwner: string;
casePermissions: {
crud: boolean;
read: boolean;
} | null;
afterCaseSelection?: Function;
columns: ColumnHeaderOptions[];
dataViewId?: string | null;
defaultCellActions?: TGridCellAction[];
@ -127,10 +117,6 @@ export interface TGridStandaloneProps {
}
const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
afterCaseSelection,
appId,
casesOwner,
casePermissions,
columns,
dataViewId = null,
defaultCellActions,
@ -272,26 +258,6 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
);
const hasAlerts = totalCountMinusDeleted > 0;
const activeCaseFlowId = useSelector((state: State) => tGridSelectors.activeCaseFlowId(state));
const selectedEvent = useMemo(() => {
const matchedEvent = events.find((event) => event.ecs._id === activeCaseFlowId);
if (matchedEvent) {
return matchedEvent;
} else {
return undefined;
}
}, [events, activeCaseFlowId]);
const addToCaseActionProps = useMemo(() => {
return {
event: selectedEvent,
casePermissions: casePermissions ?? null,
appId,
owner: casesOwner,
onClose: afterCaseSelection,
};
}, [appId, casePermissions, afterCaseSelection, selectedEvent, casesOwner]);
const nonDeletedEvents = useMemo(
() => events.filter((e) => !deletedEventIds.includes(e._id)),
[deletedEventIds, events]
@ -425,7 +391,6 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
</EventsContainerLoading>
</TimelineContext.Provider>
) : null}
<AddToCaseAction {...addToCaseActionProps} casesFeatures={casesFeatures} />
</AlertsTableWrapper>
</InspectButtonContainer>
);

View file

@ -1,71 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { normalizedEventFields } from './use_add_to_case';
import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils';
import { merge } from 'lodash';
const defaultArgs = {
_id: 'test-id',
data: [
{ field: '@timestamp', value: ['2018-11-05T19:03:25.937Z'] },
{ field: ALERT_RULE_UUID, value: ['data-rule-id'] },
{ field: ALERT_RULE_NAME, value: ['data-rule-name'] },
],
ecs: {
_id: 'test-id',
_index: 'test-index',
signal: { rule: { id: ['rule-id'], name: ['rule-name'], false_positives: [] } },
},
};
describe('normalizedEventFields', () => {
it('uses rule data when provided', () => {
const result = normalizedEventFields(defaultArgs);
expect(result).toEqual({
ruleId: 'data-rule-id',
ruleName: 'data-rule-name',
});
});
const makeObj = (s: string, v: string[]) => {
const keys = s.split('.');
return keys
.reverse()
.reduce((prev, current, i) => (i === 0 ? { [current]: v } : { [current]: { ...prev } }), {});
};
it('uses rule/ecs combo Xavier thinks is a thing but Steph has yet to see', () => {
const args = {
...defaultArgs,
data: [],
ecs: {
_id: 'string',
...merge(
makeObj(ALERT_RULE_UUID, ['xavier-rule-id']),
makeObj(ALERT_RULE_NAME, ['xavier-rule-name'])
),
},
};
const result = normalizedEventFields(args);
expect(result).toEqual({
ruleId: 'xavier-rule-id',
ruleName: 'xavier-rule-name',
});
});
it('falls back to use ecs data', () => {
const result = normalizedEventFields({ ...defaultArgs, data: [] });
expect(result).toEqual({
ruleId: 'rule-id',
ruleName: 'rule-name',
});
});
it('returns null when all the data is bad', () => {
const result = normalizedEventFields({ ...defaultArgs, data: [], ecs: { _id: 'bad' } });
expect(result).toEqual({
ruleId: null,
ruleName: null,
});
});
});

View file

@ -1,200 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { get, isEmpty } from 'lodash/fp';
import { useState, useCallback, useMemo, SyntheticEvent } from 'react';
import { useDispatch } from 'react-redux';
import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
import { Case, CommentType } from '../../../cases/common';
import { TimelinesStartServices } from '../types';
import { TimelineItem } from '../../common/search_strategy';
import { tGridActions } from '../store/t_grid';
import { useDeepEqualSelector } from './use_selector';
import { createUpdateSuccessToaster } from '../components/actions/timeline/cases/helpers';
import { AddToCaseActionProps } from '../components/actions';
import { CaseAttachments, CasesDeepLinkId, generateCaseViewPath } from '../../../cases/public';
interface UseAddToCase {
addNewCaseClick: () => void;
addExistingCaseClick: () => void;
onCaseClicked: (theCase?: Case) => void;
onCaseSuccess: (theCase: Case) => Promise<void>;
onCaseCreated: () => Promise<void>;
isAllCaseModalOpen: boolean;
isDisabled: boolean;
userCanCrud: boolean;
isEventSupported: boolean;
openPopover: (event: SyntheticEvent<HTMLButtonElement, MouseEvent>) => void;
closePopover: () => void;
isPopoverOpen: boolean;
isCreateCaseFlyoutOpen: boolean;
caseAttachments?: CaseAttachments;
}
export const useAddToCase = ({
event,
casePermissions,
appId,
onClose,
owner,
}: AddToCaseActionProps): UseAddToCase => {
const eventId = event?.ecs._id ?? '';
const dispatch = useDispatch();
// TODO: use correct value in standalone or integrated.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const timelineById = useDeepEqualSelector((state: any) => {
if (state.timeline) {
return state.timeline.timelineById[eventId];
} else {
return state.timelineById[eventId];
}
});
const isAllCaseModalOpen = useMemo(() => {
if (timelineById) {
return timelineById.isAddToExistingCaseOpen;
} else {
return false;
}
}, [timelineById]);
const isCreateCaseFlyoutOpen = useMemo(() => {
if (timelineById) {
return timelineById.isCreateNewCaseOpen;
} else {
return false;
}
}, [timelineById]);
const {
application: { navigateToApp },
notifications: { toasts },
} = useKibana<TimelinesStartServices>().services;
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const openPopover = useCallback(() => setIsPopoverOpen(true), []);
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
const isEventSupported = useMemo(() => {
if (event !== undefined) {
if (event.data.some(({ field }) => field === 'kibana.alert.rule.uuid')) {
return true;
}
return !isEmpty(event.ecs.signal?.rule?.id ?? event.ecs.kibana?.alert?.rule?.uuid);
} else {
return false;
}
}, [event]);
const userCanCrud = casePermissions?.crud ?? false;
const isDisabled = !userCanCrud || !isEventSupported;
const onViewCaseClick = useCallback(
(id) => {
navigateToApp(appId, {
deepLinkId: CasesDeepLinkId.cases,
path: generateCaseViewPath({ detailName: id }),
});
},
[navigateToApp, appId]
);
const onCaseCreated = useCallback(async () => {
dispatch(tGridActions.setOpenAddToNewCase({ id: eventId, isOpen: false }));
}, [eventId, dispatch]);
const onCaseSuccess = useCallback(
async (theCase: Case) => {
dispatch(tGridActions.setOpenAddToExistingCase({ id: eventId, isOpen: false }));
createUpdateSuccessToaster(toasts, theCase, onViewCaseClick);
},
[onViewCaseClick, toasts, dispatch, eventId]
);
const caseAttachments: CaseAttachments = useMemo(() => {
const eventIndex = event?.ecs._index ?? '';
const { ruleId, ruleName } = normalizedEventFields(event);
const attachments = [
{
alertId: eventId,
index: eventIndex ?? '',
rule: {
id: ruleId,
name: ruleName,
},
owner,
type: CommentType.alert as const,
},
];
return attachments;
}, [event, eventId, owner]);
const onCaseClicked = useCallback(
(theCase?: Case) => {
/**
* No cases listed on the table.
* The user pressed the add new case table's button.
* We gonna open the create case modal.
*/
if (theCase == null) {
dispatch(tGridActions.setOpenAddToNewCase({ id: eventId, isOpen: true }));
}
dispatch(tGridActions.setOpenAddToExistingCase({ id: eventId, isOpen: false }));
},
[dispatch, eventId]
);
const addNewCaseClick = useCallback(() => {
closePopover();
dispatch(tGridActions.setOpenAddToNewCase({ id: eventId, isOpen: true }));
if (onClose) {
onClose();
}
}, [onClose, closePopover, dispatch, eventId]);
const addExistingCaseClick = useCallback(() => {
closePopover();
dispatch(tGridActions.setOpenAddToExistingCase({ id: eventId, isOpen: true }));
if (onClose) {
onClose();
}
}, [onClose, closePopover, dispatch, eventId]);
return {
caseAttachments,
addNewCaseClick,
addExistingCaseClick,
onCaseClicked,
onCaseSuccess,
onCaseCreated,
isAllCaseModalOpen,
isDisabled,
userCanCrud,
isEventSupported,
openPopover,
closePopover,
isPopoverOpen,
isCreateCaseFlyoutOpen,
};
};
export function normalizedEventFields(event?: TimelineItem) {
const ruleUuidData = event && event.data.find(({ field }) => field === ALERT_RULE_UUID);
const ruleNameData = event && event.data.find(({ field }) => field === ALERT_RULE_NAME);
const ruleUuidValueData = ruleUuidData && ruleUuidData.value && ruleUuidData.value[0];
const ruleNameValueData = ruleNameData && ruleNameData.value && ruleNameData.value[0];
const ruleUuid =
ruleUuidValueData ??
get(`ecs.${ALERT_RULE_UUID}[0]`, event) ??
get(`ecs.signal.rule.id[0]`, event) ??
null;
const ruleName =
ruleNameValueData ??
get(`ecs.${ALERT_RULE_NAME}[0]`, event) ??
get(`ecs.signal.rule.name[0]`, event) ??
null;
return {
ruleId: ruleUuid,
ruleName,
};
}

View file

@ -7,14 +7,11 @@
import React, { lazy, Suspense } from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { I18nProvider } from '@kbn/i18n-react';
import type { Store } from 'redux';
import { Provider } from 'react-redux';
import type { Storage } from '../../../../../src/plugins/kibana_utils/public';
import type { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
import type { TGridProps } from '../types';
import type { LastUpdatedAtProps, LoadingPanelProps, FieldBrowserProps } from '../components';
import type { AddToCaseActionProps } from '../components/actions/timeline/cases/add_to_case_action';
import { initialTGridState } from '../store/t_grid/reducer';
import { createStore } from '../store/t_grid';
import { TGridLoading } from '../components/t_grid/shared';
@ -84,76 +81,3 @@ export const getFieldsBrowserLazy = (props: FieldBrowserProps, { store }: { stor
</Suspense>
);
};
const AddToCaseLazy = lazy(() => import('../components/actions/timeline/cases/add_to_case_action'));
export const getAddToCaseLazy = (
props: AddToCaseActionProps,
{ store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
) => {
return (
<Suspense fallback={<span />}>
<Provider store={store}>
<I18nProvider>
<AddToCaseLazy {...props} />
</I18nProvider>
</Provider>
</Suspense>
);
};
const AddToCasePopover = lazy(
() => import('../components/actions/timeline/cases/add_to_case_action_button')
);
export const getAddToCasePopoverLazy = (
props: AddToCaseActionProps,
{ store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
) => {
initializeStore({ store, storage, setStore });
return (
<Suspense fallback={<span />}>
<Provider store={store}>
<I18nProvider>
<AddToCasePopover {...props} />
</I18nProvider>
</Provider>
</Suspense>
);
};
const AddToExistingButton = lazy(
() => import('../components/actions/timeline/cases/add_to_existing_case_button')
);
export const getAddToExistingCaseButtonLazy = (
props: AddToCaseActionProps,
{ store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
) => {
initializeStore({ store, storage, setStore });
return (
<Suspense fallback={<EuiLoadingSpinner />}>
<Provider store={store}>
<I18nProvider>
<AddToExistingButton {...props} />
</I18nProvider>
</Provider>
</Suspense>
);
};
const AddToNewCaseButton = lazy(
() => import('../components/actions/timeline/cases/add_to_new_case_button')
);
export const getAddToNewCaseButtonLazy = (
props: AddToCaseActionProps,
{ store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
) => {
initializeStore({ store, storage, setStore });
return (
<Suspense fallback={<EuiLoadingSpinner />}>
<Provider store={store}>
<I18nProvider>
<AddToNewCaseButton {...props} />
</I18nProvider>
</Provider>
</Suspense>
);
};

View file

@ -34,8 +34,6 @@ export const mockGlobalState: TimelineState = {
'packetbeat-*',
'winlogbeat-*',
],
isAddToExistingCaseOpen: false,
isCreateNewCaseOpen: false,
isLoading: false,
isSelectAllChecked: false,
itemsPerPage: 5,

View file

@ -1566,8 +1566,6 @@ export const mockTgridModel: TGridModel = {
selectAll: false,
id: 'ef579e40-jibber-jabber',
indexNames: [],
isAddToExistingCaseOpen: false,
isCreateNewCaseOpen: false,
isLoading: false,
isSelectAllChecked: false,
kqlQuery: {

View file

@ -24,6 +24,4 @@ export const createTGridMocks = () => ({
getUseAddToTimeline: () => useAddToTimeline,
getUseAddToTimelineSensor: () => useAddToTimelineSensor,
getUseDraggableKeyboardWrapper: () => useDraggableKeyboardWrapper,
getAddToExistingCaseButton: () => <div data-test-subj="add-to-existing-case" />,
getAddToNewCaseButton: () => <div data-test-subj="add-to-new-case" />,
});

View file

@ -16,10 +16,6 @@ import {
getLoadingPanelLazy,
getTGridLazy,
getFieldsBrowserLazy,
getAddToCaseLazy,
getAddToExistingCaseButtonLazy,
getAddToNewCaseButtonLazy,
getAddToCasePopoverLazy,
} from './methods';
import type { TimelinesUIStart, TGridProps, TimelinesStartPlugins } from './types';
import { tGridReducer } from './store/t_grid/reducer';
@ -88,38 +84,6 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
setTGridEmbeddedStore: (store: Store) => {
this.setStore(store);
},
getAddToCaseAction: (props) => {
return getAddToCaseLazy(props, {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
store: this._store!,
storage: this._storage,
setStore: this.setStore.bind(this),
});
},
getAddToCasePopover: (props) => {
return getAddToCasePopoverLazy(props, {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
store: this._store!,
storage: this._storage,
setStore: this.setStore.bind(this),
});
},
getAddToExistingCaseButton: (props) => {
return getAddToExistingCaseButtonLazy(props, {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
store: this._store!,
storage: this._storage,
setStore: this.setStore.bind(this),
});
},
getAddToNewCaseButton: (props) => {
return getAddToNewCaseButtonLazy(props, {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
store: this._store!,
storage: this._storage,
setStore: this.setStore.bind(this),
});
},
};
}

View file

@ -117,11 +117,3 @@ export const setTimelineUpdatedAt =
export const addProviderToTimeline = actionCreator<{ id: string; dataProvider: DataProvider }>(
'ADD_PROVIDER_TO_TIMELINE'
);
export const setOpenAddToExistingCase = actionCreator<{ id: string; isOpen: boolean }>(
'SET_OPEN_ADD_TO_EXISTING_CASE'
);
export const setOpenAddToNewCase = actionCreator<{ id: string; isOpen: boolean }>(
'SET_OPEN_ADD_TO_NEW_CASE'
);

View file

@ -66,8 +66,6 @@ export interface TGridModel extends TGridModelSettings {
/** Uniquely identifies the timeline */
id: string;
indexNames: string[];
isAddToExistingCaseOpen: boolean;
isCreateNewCaseOpen: boolean;
isLoading: boolean;
/** If selectAll checkbox in header is checked **/
isSelectAllChecked: boolean;

View file

@ -18,8 +18,6 @@ import {
setEventsDeleted,
setEventsLoading,
setTGridSelectAll,
setOpenAddToExistingCase,
setOpenAddToNewCase,
setSelected,
setTimelineUpdatedAt,
toggleDetailPanel,
@ -239,26 +237,6 @@ export const tGridReducer = reducerWithInitialState(initialTGridState)
...state,
timelineById: addProviderToTimelineHelper(id, dataProvider, state.timelineById),
}))
.case(setOpenAddToExistingCase, (state, { id, isOpen }) => ({
...state,
timelineById: {
...state.timelineById,
[id]: {
...state.timelineById[id],
isAddToExistingCaseOpen: isOpen,
},
},
}))
.case(setOpenAddToNewCase, (state, { id, isOpen }) => ({
...state,
timelineById: {
...state.timelineById,
[id]: {
...state.timelineById[id],
isCreateNewCaseOpen: isOpen,
},
},
}))
.case(setTimelineUpdatedAt, (state, { id, updated }) => ({
...state,
timelineById: {

View file

@ -6,26 +6,11 @@
*/
import { getOr } from 'lodash/fp';
import { createSelector } from 'reselect';
import { TGridModel, State } from '.';
import { TGridModel } from '.';
import { tGridDefaults, getTGridManageDefaults } from './defaults';
interface TGridById {
[id: string]: TGridModel;
}
const getDefaultTgrid = (id: string) => ({ ...tGridDefaults, ...getTGridManageDefaults(id) });
const standaloneTGridById = (state: State): TGridById => state.timelineById;
export const activeCaseFlowId = createSelector(standaloneTGridById, (tGrid) => {
return (
tGrid &&
Object.entries(tGrid)
.map(([id, data]) => (data.isAddToExistingCaseOpen || data.isCreateNewCaseOpen ? id : null))
.find((id) => id)
);
});
export const selectTGridById = (state: unknown, timelineId: string): TGridModel => {
return getOr(
getOr(getDefaultTgrid(timelineId), ['timelineById', timelineId], state),

View file

@ -23,7 +23,6 @@ import type { TGridIntegratedProps } from './components/t_grid/integrated';
import type { TGridStandaloneProps } from './components/t_grid/standalone';
import type { UseAddToTimelineProps, UseAddToTimeline } from './hooks/use_add_to_timeline';
import { HoverActionsConfig } from './components/hover_actions/index';
import type { AddToCaseActionProps } from './components/actions/timeline/cases/add_to_case_action';
import { TimelineTabs } from '../common/types';
export * from './store/t_grid';
export interface TimelinesUIStart {
@ -42,10 +41,6 @@ export interface TimelinesUIStart {
props: UseDraggableKeyboardWrapperProps
) => UseDraggableKeyboardWrapper;
setTGridEmbeddedStore: (store: Store) => void;
getAddToCaseAction: (props: AddToCaseActionProps) => ReactElement<AddToCaseActionProps>;
getAddToCasePopover: (props: AddToCaseActionProps) => ReactElement<AddToCaseActionProps>;
getAddToExistingCaseButton: (props: AddToCaseActionProps) => ReactElement<AddToCaseActionProps>;
getAddToNewCaseButton: (props: AddToCaseActionProps) => ReactElement<AddToCaseActionProps>;
}
export interface TimelinesStartPlugins {

View file

@ -27003,16 +27003,6 @@
"xpack.timelines.alerts.summaryView.options.summaryView.description": "各アラートのイベントフローのレンダリングを表示",
"xpack.timelines.beatFields.errorSearchDescription": "Beatフィールドの取得でエラーが発生しました",
"xpack.timelines.beatFields.failSearchDescription": "Beat フィールドで検索を実行できませんでした",
"xpack.timelines.cases.timeline.actions.addCase": "ケースに追加",
"xpack.timelines.cases.timeline.actions.addExistingCase": "既存のケースに追加",
"xpack.timelines.cases.timeline.actions.addNewCase": "新しいケースに追加",
"xpack.timelines.cases.timeline.actions.addToCaseAriaLabel": "アラートをケースに関連付ける",
"xpack.timelines.cases.timeline.actions.addToCaseTooltip": "ケースに追加",
"xpack.timelines.cases.timeline.actions.caseCreatedSuccessToast": "アラートが「{title}」に追加されました",
"xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastText": "このケースのアラートはステータスがケースステータスと同期されました",
"xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "ケースの表示",
"xpack.timelines.cases.timeline.actions.permissionsMessage": "現在、アラートをケースに関連付けるための必要な権限がありません。サポートについては、管理者にお問い合わせください。",
"xpack.timelines.cases.timeline.actions.unsupportedEventsMessage": "このイベントはケースに関連付けられません",
"xpack.timelines.clipboard.copied": "コピー完了",
"xpack.timelines.clipboard.copy": "コピー",
"xpack.timelines.clipboard.copy.successToastTitle": "フィールド{field}をクリップボードにコピーしました",

View file

@ -27035,16 +27035,6 @@
"xpack.timelines.alerts.summaryView.options.summaryView.description": "查看每个告警的事件渲染",
"xpack.timelines.beatFields.errorSearchDescription": "获取 Beat 字段时发生错误",
"xpack.timelines.beatFields.failSearchDescription": "无法对 Beat 字段执行搜索",
"xpack.timelines.cases.timeline.actions.addCase": "添加到案例",
"xpack.timelines.cases.timeline.actions.addExistingCase": "添加到现有案例",
"xpack.timelines.cases.timeline.actions.addNewCase": "添加到新案例",
"xpack.timelines.cases.timeline.actions.addToCaseAriaLabel": "将告警附加到案例",
"xpack.timelines.cases.timeline.actions.addToCaseTooltip": "添加到案例",
"xpack.timelines.cases.timeline.actions.caseCreatedSuccessToast": "告警已添加到“{title}”",
"xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastText": "此案例中的告警的状态已经与案例状态同步",
"xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "查看案例",
"xpack.timelines.cases.timeline.actions.permissionsMessage": "您当前缺少所需的权限,无法向案例附加告警。有关进一步帮助,请联系您的管理员。",
"xpack.timelines.cases.timeline.actions.unsupportedEventsMessage": "此事件无法附加到案例",
"xpack.timelines.clipboard.copied": "已复制",
"xpack.timelines.clipboard.copy": "复制",
"xpack.timelines.clipboard.copy.successToastTitle": "已将字段 {field} 复制到剪贴板",

View file

@ -65,13 +65,7 @@ const AppRoot = React.memo(
{(timelinesPluginSetup &&
timelinesPluginSetup.getTGrid &&
timelinesPluginSetup.getTGrid<'standalone'>({
appId: 'securitySolution',
casesOwner: 'securitySolutionUI',
type: 'standalone',
casePermissions: {
read: true,
crud: true,
},
columns: [],
indexNames: [],
deletedEventIds: [],