[Security Solution][RAC] Migrate add to case action to timelines plugin (#106205)
* First pass add to case action in timelines plugin * Fix fake duplicate import lint rule and some type errors * Fix some tests * Remove use_insert_timeline and pass as prop * Remove unneeded ports, fix types/tests * Finish fixing types and tests for add to case action * Remove duplicated security_solution code * Pass appId as props * Fix lint and a type error * Use react-router-dom instead of window.location.search * Fix broken test * Remove unused imports * Remove unused export and related code
This commit is contained in:
parent
dfb1b615e9
commit
3612a7a300
|
@ -895,7 +895,8 @@ module.exports = {
|
|||
{
|
||||
files: ['x-pack/plugins/cases/**/*.{js,mjs,ts,tsx}'],
|
||||
rules: {
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-duplicate-imports': 'off',
|
||||
'@typescript-eslint/no-duplicate-imports': ['error'],
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -912,6 +913,8 @@ module.exports = {
|
|||
],
|
||||
rules: {
|
||||
'import/no-nodejs-modules': 'error',
|
||||
'no-duplicate-imports': 'off',
|
||||
'@typescript-eslint/no-duplicate-imports': ['error'],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
|
@ -954,7 +957,7 @@ module.exports = {
|
|||
'no-continue': 'error',
|
||||
'no-dupe-keys': 'error',
|
||||
'no-duplicate-case': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-duplicate-imports': 'off',
|
||||
'no-empty-character-class': 'error',
|
||||
'no-empty-pattern': 'error',
|
||||
'no-ex-assign': 'error',
|
||||
|
@ -1025,6 +1028,7 @@ module.exports = {
|
|||
'require-atomic-updates': 'error',
|
||||
'symbol-description': 'error',
|
||||
'vars-on-top': 'error',
|
||||
'@typescript-eslint/no-duplicate-imports': ['error'],
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -1,55 +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 'jest-styled-components';
|
||||
import { createUpdateSuccessToaster } from './helpers';
|
||||
import { Case } from '../../../../../cases/common';
|
||||
|
||||
const theCase = {
|
||||
id: 'case-id',
|
||||
title: 'My case',
|
||||
settings: {
|
||||
syncAlerts: true,
|
||||
},
|
||||
} as Case;
|
||||
|
||||
describe('helpers', () => {
|
||||
const onViewCaseClick = jest.fn();
|
||||
|
||||
describe('createUpdateSuccessToaster', () => {
|
||||
it('creates the correct toast when the sync alerts is on', () => {
|
||||
// We remove the id as is randomly generated and the text as it is a React component
|
||||
// which is being test on toaster_content.test.tsx
|
||||
const { id, text, title, ...toast } = createUpdateSuccessToaster(theCase, onViewCaseClick);
|
||||
const mountedTitle = mount(<>{title}</>);
|
||||
|
||||
expect(toast).toEqual({
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
});
|
||||
expect(mountedTitle).toMatchInlineSnapshot(`
|
||||
.c0 {
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
<styled.span>
|
||||
<span
|
||||
className="c0"
|
||||
>
|
||||
An alert has been added to "My case"
|
||||
</span>
|
||||
</styled.span>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,11 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-duplicate-imports */
|
||||
import type ResizeObserver from 'resize-observer-polyfill';
|
||||
import type React from 'react';
|
||||
import { Store } from 'redux';
|
||||
import { Middleware, Dispatch } from 'redux';
|
||||
import { Store, Middleware, Dispatch } from 'redux';
|
||||
import { BBox } from 'rbush';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ResolverAction } from './store/actions';
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
|
||||
/* eslint-disable @elastic/eui/href-or-on-click */
|
||||
|
||||
/* eslint-disable no-duplicate-imports */
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
|
@ -22,7 +20,6 @@ import {
|
|||
EuiInMemoryTable,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SideEffectContext } from '../side_effect_context';
|
||||
import { StyledPanel } from '../styles';
|
||||
import {
|
||||
|
|
|
@ -5,14 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiCode } from '@elastic/eui';
|
||||
|
||||
/* eslint-disable no-duplicate-imports */
|
||||
|
||||
import { EuiBreadcrumbs } from '@elastic/eui';
|
||||
import { EuiCode, EuiBreadcrumbs, EuiDescriptionList } from '@elastic/eui';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { EuiDescriptionList } from '@elastic/eui';
|
||||
|
||||
/**
|
||||
* Used by the nodeDetail view to show attributes of the related events.
|
||||
|
|
|
@ -20,6 +20,14 @@ jest.mock('../../../../../common/hooks/use_selector', () => ({
|
|||
useShallowEqualSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana', () => {
|
||||
const useKibana = jest.requireActual('../../../../../common/lib/kibana');
|
||||
return {
|
||||
...useKibana,
|
||||
useGetUserCasesPermissions: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Actions', () => {
|
||||
beforeEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel);
|
||||
|
|
|
@ -22,10 +22,12 @@ import { InvestigateInTimelineAction } from '../../../../../detections/component
|
|||
import { AddEventNoteAction } from '../actions/add_note_icon_item';
|
||||
import { PinEventAction } from '../actions/pin_event_action';
|
||||
import { EventsTdContent } from '../../styles';
|
||||
import { useKibana, useGetUserCasesPermissions } from '../../../../../common/lib/kibana';
|
||||
import { APP_ID } from '../../../../../../common/constants';
|
||||
import * as i18n from '../translations';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import { AddToCaseAction } from '../../../../../cases/components/timeline_actions/add_to_case_action';
|
||||
import { useInsertTimeline } from '../../../../../cases/components/use_insert_timeline';
|
||||
import { TimelineId, ActionProps, OnPinEvent } from '../../../../../../common/types/timeline';
|
||||
import { timelineActions, timelineSelectors } from '../../../../store/timeline';
|
||||
import { timelineDefaults } from '../../../../store/timeline/defaults';
|
||||
|
@ -59,6 +61,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
const dispatch = useDispatch();
|
||||
const emptyNotes: string[] = [];
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const { timelines: timelinesUi } = useKibana().services;
|
||||
|
||||
const onPinEvent: OnPinEvent = useCallback(
|
||||
(evtId) => dispatch(timelineActions.pinEvent({ id: timelineId, eventId: evtId })),
|
||||
|
@ -93,12 +96,21 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).timelineType
|
||||
);
|
||||
const eventType = getEventType(ecsData);
|
||||
|
||||
const casePermissions = useGetUserCasesPermissions();
|
||||
const insertTimelineHook = useInsertTimeline;
|
||||
const isEventContextMenuEnabledForEndpoint = useMemo(
|
||||
() => ecsData.event?.kind?.includes('event') && ecsData.agent?.type?.includes('endpoint'),
|
||||
[ecsData.event?.kind, ecsData.agent?.type]
|
||||
);
|
||||
|
||||
const addToCaseActionProps = useMemo(() => {
|
||||
return {
|
||||
ariaLabel: i18n.ATTACH_ALERT_TO_CASE_FOR_ROW({ ariaRowindex, columnValues }),
|
||||
ecsRowData: ecsData,
|
||||
useInsertTimeline: insertTimelineHook,
|
||||
casePermissions,
|
||||
appId: APP_ID,
|
||||
};
|
||||
}, [ariaRowindex, ecsData, casePermissions, insertTimelineHook, columnValues]);
|
||||
return (
|
||||
<ActionsContainer>
|
||||
{showCheckboxes && (
|
||||
|
@ -169,13 +181,8 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
TimelineId.detectionsPage,
|
||||
TimelineId.detectionsRulesDetailsPage,
|
||||
TimelineId.active,
|
||||
].includes(timelineId as TimelineId) && (
|
||||
<AddToCaseAction
|
||||
ariaLabel={i18n.ATTACH_ALERT_TO_CASE_FOR_ROW({ ariaRowindex, columnValues })}
|
||||
key="attach-to-case"
|
||||
ecsRowData={ecsData}
|
||||
/>
|
||||
)}
|
||||
].includes(timelineId as TimelineId) &&
|
||||
timelinesUi.getAddToCaseAction(addToCaseActionProps)}
|
||||
<AlertContextMenu
|
||||
ariaLabel={i18n.MORE_ACTIONS_FOR_ROW({ ariaRowindex, columnValues })}
|
||||
key="alert-context-menu"
|
||||
|
|
|
@ -23,16 +23,33 @@ import { testLeadingControlColumn } from '../../../../../common/mock/mock_timeli
|
|||
|
||||
jest.mock('../../../../../common/hooks/use_experimental_features');
|
||||
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_selector');
|
||||
|
||||
jest.mock('../../../../../cases/components/timeline_actions/add_to_case_action', () => {
|
||||
return {
|
||||
AddToCaseAction: () => {
|
||||
return <div data-test-subj="add-to-case-action">{'Add to case'}</div>;
|
||||
jest.mock('../../../../../common/lib/kibana', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
timelines: {
|
||||
getAddToCaseAction: () => <div data-test-subj="add-to-case-action">{'Add to case'}</div>,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}),
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
addError: jest.fn(),
|
||||
addSuccess: jest.fn(),
|
||||
addWarning: jest.fn(),
|
||||
}),
|
||||
useGetUserCasesPermissions: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../../../../../../timelines/public/components/actions/timeline/cases/add_to_case_action',
|
||||
() => {
|
||||
return {
|
||||
AddToCaseAction: () => {
|
||||
return <div data-test-subj="add-to-case-action">{'Add to case'}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
describe('EventColumnView', () => {
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
|
||||
import { KqlMode, TimelineModel } from './model';
|
||||
import { InsertTimeline } from './types';
|
||||
import { FieldsEqlOptions } from '../../../../common/search_strategy/timeline';
|
||||
import {
|
||||
TimelineEventsType,
|
||||
|
@ -23,7 +24,6 @@ import {
|
|||
TimelinePersistInput,
|
||||
SerializedFilterQuery,
|
||||
} from '../../../../common/types/timeline';
|
||||
import { InsertTimeline } from './types';
|
||||
import { tGridActions } from '../../../../../timelines/public';
|
||||
export const {
|
||||
applyDeltaToColumnWidth,
|
||||
|
@ -55,6 +55,10 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI
|
|||
'ADD_NOTE_TO_EVENT'
|
||||
);
|
||||
|
||||
export const showTimeline = actionCreator<{ id: string; show: boolean }>('SHOW_TIMELINE');
|
||||
|
||||
export const setInsertTimeline = actionCreator<InsertTimeline | null>('SET_INSERT_TIMELINE');
|
||||
|
||||
export const addProvider = actionCreator<{ id: string; provider: DataProvider }>('ADD_PROVIDER');
|
||||
|
||||
export const saveTimeline = actionCreator<TimelinePersistInput>('SAVE_TIMELINE');
|
||||
|
@ -69,8 +73,6 @@ export const removeProvider = actionCreator<{
|
|||
andProviderId?: string;
|
||||
}>('REMOVE_PROVIDER');
|
||||
|
||||
export const showTimeline = actionCreator<{ id: string; show: boolean }>('SHOW_TIMELINE');
|
||||
|
||||
export const updateTimelineGraphEventId = actionCreator<{ id: string; graphEventId: string }>(
|
||||
'UPDATE_TIMELINE_GRAPH_EVENT_ID'
|
||||
);
|
||||
|
@ -88,8 +90,6 @@ export const addTimeline = actionCreator<{
|
|||
savedTimeline?: boolean;
|
||||
}>('ADD_TIMELINE');
|
||||
|
||||
export const setInsertTimeline = actionCreator<InsertTimeline | null>('SET_INSERT_TIMELINE');
|
||||
|
||||
export const startTimelineSaving = actionCreator<{
|
||||
id: string;
|
||||
}>('START_TIMELINE_SAVING');
|
||||
|
|
|
@ -364,4 +364,12 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
},
|
||||
},
|
||||
}))
|
||||
.case(setInsertTimeline, (state, insertTimeline) => ({
|
||||
...state,
|
||||
insertTimeline,
|
||||
}))
|
||||
.case(showTimeline, (state, { id, show }) => ({
|
||||
...state,
|
||||
timelineById: updateTimelineShowTimeline({ id, show, timelineById: state.timelineById }),
|
||||
}))
|
||||
.build();
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
"extraPublicDirs": ["common"],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["alerting", "data", "dataEnhanced", "kibanaReact", "kibanaUtils"],
|
||||
"requiredPlugins": ["alerting", "cases", "data", "dataEnhanced", "kibanaReact", "kibanaUtils"],
|
||||
"optionalPlugins": []
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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, { MouseEvent } from 'react';
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { EventsTdContent } from '../t_grid/styles';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../t_grid/helpers';
|
||||
|
||||
interface ActionIconItemProps {
|
||||
ariaLabel?: string;
|
||||
width?: number;
|
||||
dataTestSubj?: string;
|
||||
content?: string;
|
||||
iconType?: string;
|
||||
isDisabled?: boolean;
|
||||
onClick?: (event: MouseEvent) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ActionIconItemComponent: React.FC<ActionIconItemProps> = ({
|
||||
width = DEFAULT_ICON_BUTTON_WIDTH,
|
||||
dataTestSubj,
|
||||
content,
|
||||
ariaLabel,
|
||||
iconType = '',
|
||||
isDisabled = false,
|
||||
onClick,
|
||||
children,
|
||||
}) => (
|
||||
<div>
|
||||
<EventsTdContent textAlign="center" width={width}>
|
||||
{children ?? (
|
||||
<EuiToolTip data-test-subj={`${dataTestSubj}-tool-tip`} content={content}>
|
||||
<EuiButtonIcon
|
||||
aria-label={ariaLabel}
|
||||
data-test-subj={`${dataTestSubj}-button`}
|
||||
iconType={iconType}
|
||||
isDisabled={isDisabled}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
</EventsTdContent>
|
||||
</div>
|
||||
);
|
||||
|
||||
ActionIconItemComponent.displayName = 'ActionIconItemComponent';
|
||||
|
||||
export const ActionIconItem = React.memo(ActionIconItemComponent);
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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';
|
|
@ -7,36 +7,17 @@
|
|||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { EuiGlobalToastList } from '@elastic/eui';
|
||||
|
||||
import { useKibana, useGetUserCasesPermissions } from '../../../common/lib/kibana';
|
||||
import { useStateToaster } from '../../../common/components/toasters';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { TestProviders, mockGetAllCasesSelectorModal } from '../../../../mock';
|
||||
import { AddToCaseAction } from './add_to_case_action';
|
||||
import { basicCase } from '../../../../../cases/public/containers/mock';
|
||||
import { Case, SECURITY_SOLUTION_OWNER } from '../../../../../cases/common';
|
||||
import { APP_ID, SecurityPageName } from '../../../../common/constants';
|
||||
import { SECURITY_SOLUTION_OWNER } from '../../../../../../cases/common';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
jest.mock('../../../common/components/link_to', () => {
|
||||
const original = jest.requireActual('../../../common/components/link_to');
|
||||
return {
|
||||
...original,
|
||||
useFormatUrl: jest.fn().mockReturnValue({
|
||||
formatUrl: jest.fn(),
|
||||
search: '',
|
||||
}),
|
||||
};
|
||||
});
|
||||
jest.mock('../../../common/components/toasters', () => {
|
||||
const actual = jest.requireActual('../../../common/components/toasters');
|
||||
return {
|
||||
...actual,
|
||||
useStateToaster: jest.fn(),
|
||||
};
|
||||
});
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: () => ({
|
||||
search: '',
|
||||
}),
|
||||
}));
|
||||
jest.mock('./helpers');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
describe('AddToCaseAction', () => {
|
||||
const props = {
|
||||
ecsRowData: {
|
||||
|
@ -44,29 +25,15 @@ describe('AddToCaseAction', () => {
|
|||
_index: 'test-index',
|
||||
signal: { rule: { id: ['rule-id'], name: ['rule-name'], false_positives: [] } },
|
||||
},
|
||||
casePermissions: {
|
||||
crud: true,
|
||||
read: true,
|
||||
},
|
||||
appId: 'securitySolution',
|
||||
};
|
||||
|
||||
const mockDispatchToaster = jest.fn();
|
||||
const mockNavigateToApp = jest.fn();
|
||||
const mockCreateCase = jest.fn();
|
||||
const mockAllCasesModal = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
useKibanaMock().services.application.navigateToApp = mockNavigateToApp;
|
||||
useKibanaMock().services.cases = {
|
||||
getAllCases: jest.fn(),
|
||||
getCaseView: jest.fn(),
|
||||
getConfigureCases: jest.fn(),
|
||||
getRecentCases: jest.fn(),
|
||||
getCreateCase: mockCreateCase,
|
||||
getAllCasesSelectorModal: mockAllCasesModal.mockImplementation(() => <>{'test'}</>),
|
||||
};
|
||||
(useStateToaster as jest.Mock).mockReturnValue([jest.fn(), mockDispatchToaster]);
|
||||
(useGetUserCasesPermissions as jest.Mock).mockReturnValue({
|
||||
crud: true,
|
||||
read: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('it renders', () => {
|
||||
|
@ -100,7 +67,7 @@ describe('AddToCaseAction', () => {
|
|||
|
||||
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(mockCreateCase).toHaveBeenCalled();
|
||||
expect(wrapper.find('[data-test-subj="create-case-flyout"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('it opens the all cases modal', () => {
|
||||
|
@ -113,12 +80,7 @@ describe('AddToCaseAction', () => {
|
|||
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(mockAllCasesModal.mock.calls[0][0].alertData).toEqual({
|
||||
alertId: 'test-id',
|
||||
index: 'test-index',
|
||||
rule: { id: 'rule-id', name: 'rule-name' },
|
||||
owner: SECURITY_SOLUTION_OWNER,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="all-cases-modal"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('it set rule information as null when missing', () => {
|
||||
|
@ -137,7 +99,7 @@ describe('AddToCaseAction', () => {
|
|||
|
||||
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(mockAllCasesModal.mock.calls[0][0].alertData).toEqual({
|
||||
expect(mockGetAllCasesSelectorModal.mock.calls[0][0].alertData).toEqual({
|
||||
alertId: 'test-id',
|
||||
index: 'test-index',
|
||||
rule: {
|
||||
|
@ -148,42 +110,6 @@ describe('AddToCaseAction', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('onSuccess triggers toaster that links to case view', () => {
|
||||
// @ts-ignore
|
||||
useKibanaMock().services.cases.getCreateCase = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess: (theCase: Case) => Promise<void>;
|
||||
}) => {
|
||||
onSuccess(basicCase);
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<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(mockDispatchToaster).toHaveBeenCalled();
|
||||
const toast = mockDispatchToaster.mock.calls[0][0].toast;
|
||||
|
||||
const toastWrapper = mount(
|
||||
<EuiGlobalToastList toasts={[toast]} toastLifeTimeMs={6000} dismissToast={() => {}} />
|
||||
);
|
||||
|
||||
toastWrapper
|
||||
.find('[data-test-subj="toaster-content-case-view-link"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, {
|
||||
path: '/basic-case-id',
|
||||
deepLinkId: SecurityPageName.case,
|
||||
});
|
||||
});
|
||||
|
||||
it('disabled when event type is not supported', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
|
@ -203,14 +129,16 @@ describe('AddToCaseAction', () => {
|
|||
});
|
||||
|
||||
it('hides the icon when user does not have crud permissions', () => {
|
||||
(useGetUserCasesPermissions as jest.Mock).mockReturnValue({
|
||||
crud: false,
|
||||
read: true,
|
||||
});
|
||||
|
||||
const newProps = {
|
||||
...props,
|
||||
casePermissions: {
|
||||
crud: false,
|
||||
read: true,
|
||||
},
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AddToCaseAction {...props} />
|
||||
<AddToCaseAction {...newProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { memo, useState, useCallback, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiButtonIcon,
|
||||
|
@ -16,28 +17,52 @@ import {
|
|||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { Case, CaseStatuses, StatusAll } from '../../../../../cases/common';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import { Ecs } from '../../../../common/ecs';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import {
|
||||
getCaseDetailsUrl,
|
||||
getCreateCaseUrl,
|
||||
useFormatUrl,
|
||||
} from '../../../common/components/link_to';
|
||||
import { useStateToaster } from '../../../common/components/toasters';
|
||||
import { useControl } from '../../../common/hooks/use_control';
|
||||
import { useGetUserCasesPermissions, useKibana } from '../../../common/lib/kibana';
|
||||
import { ActionIconItem } from '../../../timelines/components/timeline/body/actions/action_icon_item';
|
||||
import { CreateCaseFlyout } from '../create/flyout';
|
||||
import { Case, CaseStatuses, StatusAll } from '../../../../../../cases/common';
|
||||
import { Ecs } from '../../../../../common/ecs';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { TimelinesStartServices } from '../../../../types';
|
||||
import { ActionIconItem } from '../../action_icon_item';
|
||||
import { CreateCaseFlyout } from './create/flyout';
|
||||
import { createUpdateSuccessToaster } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface AddToCaseActionProps {
|
||||
export interface AddToCaseActionProps {
|
||||
ariaLabel?: string;
|
||||
ecsRowData: Ecs;
|
||||
useInsertTimeline?: Function;
|
||||
casePermissions: {
|
||||
crud: boolean;
|
||||
read: boolean;
|
||||
} | null;
|
||||
appId: string;
|
||||
}
|
||||
interface UseControlsReturn {
|
||||
isControlOpen: boolean;
|
||||
openControl: () => void;
|
||||
closeControl: () => void;
|
||||
}
|
||||
|
||||
const appendSearch = (search?: string) =>
|
||||
isEmpty(search) ? '' : `${search?.startsWith('?') ? search : `?${search}`}`;
|
||||
|
||||
const getCreateCaseUrl = (search?: string | null) => `/create${appendSearch(search ?? undefined)}`;
|
||||
|
||||
const getCaseDetailsUrl = ({
|
||||
id,
|
||||
search,
|
||||
subCaseId,
|
||||
}: {
|
||||
id: string;
|
||||
search?: string | null;
|
||||
subCaseId?: string;
|
||||
}) => {
|
||||
if (subCaseId) {
|
||||
return `/${encodeURIComponent(id)}/sub-cases/${encodeURIComponent(subCaseId)}${appendSearch(
|
||||
search ?? undefined
|
||||
)}`;
|
||||
}
|
||||
return `/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`;
|
||||
};
|
||||
interface PostCommentArg {
|
||||
caseId: string;
|
||||
data: {
|
||||
|
@ -54,23 +79,31 @@ interface PostCommentArg {
|
|||
const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
||||
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
|
||||
ecsRowData,
|
||||
useInsertTimeline,
|
||||
casePermissions,
|
||||
appId,
|
||||
}) => {
|
||||
const eventId = ecsRowData._id;
|
||||
const eventIndex = ecsRowData._index;
|
||||
const rule = ecsRowData.signal?.rule;
|
||||
|
||||
const {
|
||||
application: { navigateToApp },
|
||||
application: { navigateToApp, getUrlForApp },
|
||||
cases,
|
||||
} = useKibana().services;
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
notifications: { toasts },
|
||||
} = useKibana<TimelinesStartServices>().services;
|
||||
|
||||
const useControl = (): UseControlsReturn => {
|
||||
const [isControlOpen, setIsControlOpen] = useState<boolean>(false);
|
||||
const openControl = useCallback(() => setIsControlOpen(true), []);
|
||||
const closeControl = useCallback(() => setIsControlOpen(false), []);
|
||||
|
||||
return { isControlOpen, openControl, closeControl };
|
||||
};
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const openPopover = useCallback(() => setIsPopoverOpen(true), []);
|
||||
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
|
||||
const userPermissions = useGetUserCasesPermissions();
|
||||
|
||||
const isEventSupported = !isEmpty(ecsRowData.signal?.rule?.id);
|
||||
const userCanCrud = userPermissions?.crud ?? false;
|
||||
const userCanCrud = casePermissions?.crud ?? false;
|
||||
const isDisabled = !userCanCrud || !isEventSupported;
|
||||
const tooltipContext = userCanCrud
|
||||
? isEventSupported
|
||||
|
@ -80,13 +113,19 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
|
||||
const onViewCaseClick = useCallback(
|
||||
(id) => {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
navigateToApp(appId, {
|
||||
deepLinkId: appId === 'securitySolution' ? 'case' : 'cases',
|
||||
path: getCaseDetailsUrl({ id }),
|
||||
});
|
||||
},
|
||||
[navigateToApp]
|
||||
[navigateToApp, appId]
|
||||
);
|
||||
const currentSearch = useLocation().search;
|
||||
const urlSearch = useMemo(() => currentSearch, [currentSearch]);
|
||||
const createCaseUrl = useMemo(() => getUrlForApp('cases') + getCreateCaseUrl(urlSearch), [
|
||||
getUrlForApp,
|
||||
urlSearch,
|
||||
]);
|
||||
|
||||
const {
|
||||
isControlOpen: isCreateCaseFlyoutOpen,
|
||||
|
@ -112,35 +151,31 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
id: rule?.id != null ? rule.id[0] : null,
|
||||
name: rule?.name != null ? rule.name[0] : null,
|
||||
},
|
||||
owner: APP_ID,
|
||||
owner: appId,
|
||||
},
|
||||
updateCase,
|
||||
});
|
||||
}
|
||||
},
|
||||
[closeCaseFlyoutOpen, eventId, eventIndex, rule]
|
||||
[closeCaseFlyoutOpen, eventId, eventIndex, rule, appId]
|
||||
);
|
||||
const onCaseSuccess = useCallback(
|
||||
async (theCase: Case) => {
|
||||
closeCaseFlyoutOpen();
|
||||
return dispatchToaster({
|
||||
type: 'addToaster',
|
||||
toast: createUpdateSuccessToaster(theCase, onViewCaseClick),
|
||||
});
|
||||
createUpdateSuccessToaster(toasts, theCase, onViewCaseClick);
|
||||
},
|
||||
[closeCaseFlyoutOpen, dispatchToaster, onViewCaseClick]
|
||||
[closeCaseFlyoutOpen, onViewCaseClick, toasts]
|
||||
);
|
||||
|
||||
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
|
||||
const goToCreateCase = useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
return navigateToApp(appId, {
|
||||
deepLinkId: appId === 'securitySolution' ? 'case' : 'cases',
|
||||
path: getCreateCaseUrl(urlSearch),
|
||||
});
|
||||
},
|
||||
[navigateToApp, urlSearch]
|
||||
[navigateToApp, urlSearch, appId]
|
||||
);
|
||||
const [isAllCaseModalOpen, openAllCaseModal] = useState(false);
|
||||
|
||||
|
@ -208,6 +243,40 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
[ariaLabel, isDisabled, openPopover, tooltipContext]
|
||||
);
|
||||
|
||||
const getAllCasesSelectorModalProps = useMemo(() => {
|
||||
return {
|
||||
alertData: {
|
||||
alertId: eventId,
|
||||
index: eventIndex ?? '',
|
||||
rule: {
|
||||
id: rule?.id != null ? rule.id[0] : null,
|
||||
name: rule?.name != null ? rule.name[0] : null,
|
||||
},
|
||||
owner: appId,
|
||||
},
|
||||
createCaseNavigation: {
|
||||
href: createCaseUrl,
|
||||
onClick: goToCreateCase,
|
||||
},
|
||||
hiddenStatuses: [CaseStatuses.closed, StatusAll],
|
||||
onRowClick: onCaseClicked,
|
||||
updateCase: onCaseSuccess,
|
||||
userCanCrud: casePermissions?.crud ?? false,
|
||||
owner: [appId],
|
||||
};
|
||||
}, [
|
||||
casePermissions?.crud,
|
||||
onCaseSuccess,
|
||||
onCaseClicked,
|
||||
createCaseUrl,
|
||||
goToCreateCase,
|
||||
eventId,
|
||||
eventIndex,
|
||||
rule?.id,
|
||||
rule?.name,
|
||||
appId,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{userCanCrud && (
|
||||
|
@ -230,31 +299,16 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
afterCaseCreated={attachAlertToCase}
|
||||
onCloseFlyout={closeCaseFlyoutOpen}
|
||||
onSuccess={onCaseSuccess}
|
||||
useInsertTimeline={useInsertTimeline}
|
||||
appId={appId}
|
||||
/>
|
||||
)}
|
||||
{isAllCaseModalOpen &&
|
||||
cases.getAllCasesSelectorModal({
|
||||
alertData: {
|
||||
alertId: eventId,
|
||||
index: eventIndex ?? '',
|
||||
rule: {
|
||||
id: rule?.id != null ? rule.id[0] : null,
|
||||
name: rule?.name != null ? rule.name[0] : null,
|
||||
},
|
||||
owner: APP_ID,
|
||||
},
|
||||
createCaseNavigation: {
|
||||
href: formatUrl(getCreateCaseUrl()),
|
||||
onClick: goToCreateCase,
|
||||
},
|
||||
hiddenStatuses: [CaseStatuses.closed, StatusAll],
|
||||
onRowClick: onCaseClicked,
|
||||
updateCase: onCaseSuccess,
|
||||
userCanCrud: userPermissions?.crud ?? false,
|
||||
owner: [APP_ID],
|
||||
})}
|
||||
{isAllCaseModalOpen && cases.getAllCasesSelectorModal(getAllCasesSelectorModalProps)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddToCaseAction = memo(AddToCaseActionComponent);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AddToCaseAction;
|
|
@ -8,24 +8,15 @@
|
|||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import '../../../common/mock/match_media';
|
||||
import { CreateCaseFlyout } from './flyout';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { TestProviders } from '../../../../../mock';
|
||||
|
||||
jest.mock('../../../common/lib/kibana', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
cases: {
|
||||
getCreateCase: () => {},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
const onCloseFlyout = jest.fn();
|
||||
const onSuccess = jest.fn();
|
||||
const defaultProps = {
|
||||
onCloseFlyout,
|
||||
onSuccess,
|
||||
appId: 'securitySolution',
|
||||
};
|
||||
|
||||
describe('CreateCaseFlyout', () => {
|
|
@ -5,19 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
|
||||
|
||||
import * as i18n from '../../translations';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { Case } from '../../../../../cases/common';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import * as i18n from '../translations';
|
||||
import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { Case } from '../../../../../../../cases/common';
|
||||
import type { TimelinesStartServices } from '../../../../../types';
|
||||
|
||||
export interface CreateCaseModalProps {
|
||||
afterCaseCreated?: (theCase: Case) => Promise<void>;
|
||||
onCloseFlyout: () => void;
|
||||
onSuccess: (theCase: Case) => Promise<void>;
|
||||
useInsertTimeline?: Function;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
const StyledFlyout = styled(EuiFlyout)`
|
||||
|
@ -50,8 +52,18 @@ const CreateCaseFlyoutComponent: React.FC<CreateCaseModalProps> = ({
|
|||
afterCaseCreated,
|
||||
onCloseFlyout,
|
||||
onSuccess,
|
||||
appId,
|
||||
}) => {
|
||||
const { cases } = useKibana().services;
|
||||
const { cases } = useKibana<TimelinesStartServices>().services;
|
||||
const createCaseProps = useMemo(() => {
|
||||
return {
|
||||
afterCaseCreated,
|
||||
onCancel: onCloseFlyout,
|
||||
onSuccess,
|
||||
withSteps: false,
|
||||
owner: [appId],
|
||||
};
|
||||
}, [afterCaseCreated, onCloseFlyout, onSuccess, appId]);
|
||||
return (
|
||||
<StyledFlyout onClose={onCloseFlyout} data-test-subj="create-case-flyout">
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
|
@ -60,15 +72,7 @@ const CreateCaseFlyoutComponent: React.FC<CreateCaseModalProps> = ({
|
|||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<StyledEuiFlyoutBody>
|
||||
<FormWrapper>
|
||||
{cases.getCreateCase({
|
||||
afterCaseCreated,
|
||||
onCancel: onCloseFlyout,
|
||||
onSuccess,
|
||||
withSteps: false,
|
||||
owner: [APP_ID],
|
||||
})}
|
||||
</FormWrapper>
|
||||
<FormWrapper>{cases.getCreateCase(createCaseProps)}</FormWrapper>
|
||||
</StyledEuiFlyoutBody>
|
||||
</StyledFlyout>
|
||||
);
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import uuid from 'uuid';
|
||||
import styled from 'styled-components';
|
||||
import { AppToast } from '../../../common/components/toasters';
|
||||
import { ToasterContent } from './toaster_content';
|
||||
import * as i18n from './translations';
|
||||
import { Case } from '../../../../../cases/common';
|
||||
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;
|
||||
|
||||
|
@ -24,20 +24,20 @@ const Title = styled.span`
|
|||
`;
|
||||
|
||||
export const createUpdateSuccessToaster = (
|
||||
toasts: ToastsStart,
|
||||
theCase: Case,
|
||||
onViewCaseClick: (id: string) => void
|
||||
): AppToast => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
): Toast => {
|
||||
return toasts.addSuccess({
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
title: <Title>{i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title)}</Title>,
|
||||
text: (
|
||||
title: toMountPoint(<Title>{i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title)}</Title>),
|
||||
text: toMountPoint(
|
||||
<ToasterContent
|
||||
caseId={theCase.id}
|
||||
syncAlerts={theCase.settings.syncAlerts}
|
||||
onViewCaseClick={onViewCaseClick}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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';
|
|
@ -7,63 +7,60 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ACTION_ADD_CASE = i18n.translate(
|
||||
'xpack.securitySolution.cases.timeline.actions.addCase',
|
||||
{
|
||||
defaultMessage: 'Add to case',
|
||||
}
|
||||
);
|
||||
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.securitySolution.cases.timeline.actions.addNewCase',
|
||||
'xpack.timelines.cases.timeline.actions.addNewCase',
|
||||
{
|
||||
defaultMessage: 'Add to new case',
|
||||
}
|
||||
);
|
||||
|
||||
export const ACTION_ADD_EXISTING_CASE = i18n.translate(
|
||||
'xpack.securitySolution.cases.timeline.actions.addExistingCase',
|
||||
'xpack.timelines.cases.timeline.actions.addExistingCase',
|
||||
{
|
||||
defaultMessage: 'Add to existing case',
|
||||
}
|
||||
);
|
||||
|
||||
export const ACTION_ADD_TO_CASE_ARIA_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.cases.timeline.actions.addToCaseAriaLabel',
|
||||
'xpack.timelines.cases.timeline.actions.addToCaseAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Attach alert to case',
|
||||
}
|
||||
);
|
||||
|
||||
export const ACTION_ADD_TO_CASE_TOOLTIP = i18n.translate(
|
||||
'xpack.securitySolution.cases.timeline.actions.addToCaseTooltip',
|
||||
'xpack.timelines.cases.timeline.actions.addToCaseTooltip',
|
||||
{
|
||||
defaultMessage: 'Add to case',
|
||||
}
|
||||
);
|
||||
|
||||
export const CASE_CREATED_SUCCESS_TOAST = (title: string) =>
|
||||
i18n.translate('xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast', {
|
||||
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.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText',
|
||||
'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.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink',
|
||||
'xpack.timelines.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink',
|
||||
{
|
||||
defaultMessage: 'View Case',
|
||||
}
|
||||
);
|
||||
|
||||
export const PERMISSIONS_MSG = i18n.translate(
|
||||
'xpack.securitySolution.cases.timeline.actions.permissionsMessage',
|
||||
'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.',
|
||||
|
@ -71,8 +68,12 @@ export const PERMISSIONS_MSG = i18n.translate(
|
|||
);
|
||||
|
||||
export const UNSUPPORTED_EVENTS_MSG = i18n.translate(
|
||||
'xpack.securitySolution.cases.timeline.actions.unsupportedEventsMessage',
|
||||
'xpack.timelines.cases.timeline.actions.unsupportedEventsMessage',
|
||||
{
|
||||
defaultMessage: 'This event cannot be attached to a case',
|
||||
}
|
||||
);
|
||||
|
||||
export const CREATE_TITLE = i18n.translate('xpack.timelines.cases.caseView.create', {
|
||||
defaultMessage: 'Create new case',
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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';
|
|
@ -11,7 +11,6 @@ import { Dispatch } from 'redux';
|
|||
import { isString, keyBy } from 'lodash/fp';
|
||||
|
||||
import { stopPropagationAndPreventDefault, TimelineId } from '../../../common';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { BrowserField, BrowserFields, ColumnHeaderOptions } from '../../../common';
|
||||
import { tGridActions } from '../../store/t_grid';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH } from '../t_grid/body/constants';
|
||||
|
|
|
@ -28,7 +28,6 @@ import { Header } from './header';
|
|||
import * as i18n from './translations';
|
||||
import { tGridActions } from '../../../../store/t_grid';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { ColumnHeaderOptions } from '../../../../../common/types/timeline';
|
||||
|
||||
import { Direction } from '../../../../../common/search_strategy';
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { Direction } from '../../../../../../common';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { ColumnHeaderOptions } from '../../../../../../common';
|
||||
import { assertUnreachable } from '../../../../../../common/utility_types';
|
||||
import { Sort, SortDirection } from '../../sort';
|
||||
|
|
|
@ -11,7 +11,6 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|||
import { Droppable, DraggableChildrenFn } from 'react-beautiful-dnd';
|
||||
|
||||
import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
ControlColumnProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
import { StatefulCell } from './stateful_cell';
|
||||
import * as i18n from './translations';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
ActionProps,
|
||||
CellValueElementProps,
|
||||
|
|
|
@ -9,7 +9,6 @@ import React, { HTMLAttributes, useState } from 'react';
|
|||
import type { TimelineNonEcsData } from '../../../../../common/search_strategy';
|
||||
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -11,7 +11,6 @@ import type { OnRowSelected } from '../../types';
|
|||
import { EventsTrData, EventsTdGroupActions } from '../../styles';
|
||||
import { DataDrivenColumns, getMappedNonEcsValue } from '../data_driven_columns';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -12,7 +12,6 @@ import { EventsTbody } from '../../styles';
|
|||
import { StatefulEvent } from './stateful_event';
|
||||
import type { BrowserFields } from '../../../../../common/search_strategy/index_fields';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -19,7 +19,6 @@ import { getMappedNonEcsValue } from '../data_driven_columns';
|
|||
import { StatefulEventContext } from './stateful_event_context';
|
||||
import type { BrowserFields } from '../../../../../common/search_strategy/index_fields';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import { focusColumn, isArrowDownOrArrowUp, isArrowUp, isEscape } from '../../../../../../common';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { OnColumnFocused } from '../../../../../../common';
|
||||
|
||||
type FocusOwnership = 'not-owned' | 'owned';
|
||||
|
|
|
@ -18,7 +18,6 @@ import React, { ComponentType, useCallback, useEffect, useMemo, useState } from
|
|||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { RowRendererId } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { RowRenderer } from '../../../../../common/types/timeline';
|
||||
|
||||
const PlainRowRenderer = () => <></>;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { SortDirection } from '../../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { SortColumnTimeline } from '../../../../../common/types/timeline';
|
||||
|
||||
// TODO: Cleanup this type to match SortColumnTimeline
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
import type { IIndexPattern } from '../../../../../../src/plugins/data/public';
|
||||
import type { BrowserFields } from '../../../common/search_strategy/index_fields';
|
||||
import { DataProviderType, EXISTS_OPERATOR } from '../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { DataProvider, DataProvidersAnd } from '../../../common/types/timeline';
|
||||
import { convertToBuildEsQuery, escapeQueryValue } from '../utils/keury';
|
||||
|
||||
|
|
|
@ -12,12 +12,10 @@ import { useDispatch } from 'react-redux';
|
|||
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { Direction } from '../../../../common/search_strategy';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { DocValueFields } from '../../../../common/search_strategy';
|
||||
import type { CoreStart } from '../../../../../../../src/core/public';
|
||||
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -14,7 +14,6 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'
|
|||
import { Direction } from '../../../../common/search_strategy';
|
||||
import type { CoreStart } from '../../../../../../../src/core/public';
|
||||
import { TimelineTabs } from '../../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
DATA_ROWINDEX_ATTRIBUTE,
|
||||
onKeyDownFocusHandler,
|
||||
} from '../../../../../common';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { BrowserFields } from '../../../../../common';
|
||||
import { getCategoryColumns } from './category_columns';
|
||||
import { CATEGORIES_PANE_CLASS_NAME, TABLE_HEIGHT } from './helpers';
|
||||
|
|
|
@ -15,12 +15,10 @@ import {
|
|||
DATA_ROWINDEX_ATTRIBUTE,
|
||||
onKeyDownFocusHandler,
|
||||
} from '../../../../../common';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { BrowserFields, OnUpdateColumns } from '../../../../../common';
|
||||
|
||||
import { CategoryTitle } from './category_title';
|
||||
import { getFieldColumns } from './field_items';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { FieldItem } from './field_items';
|
||||
import { CATEGORY_TABLE_CLASS_NAME, TABLE_HEIGHT } from './helpers';
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import styled from 'styled-components';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import type { BrowserFields, ColumnHeaderOptions } from '../../../../../common';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import { isEscape, isTab, stopPropagationAndPreventDefault } from '../../../../../common';
|
||||
import { CategoriesPane } from './categories_pane';
|
||||
import { FieldsPane } from './fields_pane';
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
TimelineFactoryQueryTypes,
|
||||
TimelineEventsQueries,
|
||||
} from '../../common/search_strategy';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type {
|
||||
DocValueFields,
|
||||
Inspect,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { useCallback, useRef } from 'react';
|
||||
import { isString } from 'lodash/fp';
|
||||
import { isAppError, isKibanaError, isSecurityAppError } from '@kbn/securitysolution-t-grid';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { AppError } from '@kbn/securitysolution-t-grid';
|
||||
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
LoadingPanelProps,
|
||||
FieldBrowserWrappedProps,
|
||||
} from '../components';
|
||||
import type { AddToCaseActionProps } from '../components/actions/timeline/cases/add_to_case_action';
|
||||
|
||||
const TimelineLazy = lazy(() => import('../components'));
|
||||
export const getTGridLazy = (
|
||||
|
@ -68,3 +69,12 @@ export const getFieldsBrowserLazy = (
|
|||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
const AddToCaseLazy = lazy(() => import('../components/actions/timeline/cases/add_to_case_action'));
|
||||
export const getAddToCaseLazy = (props: AddToCaseActionProps) => {
|
||||
return (
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<AddToCaseLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export * from './browser_fields';
|
||||
export * from './header';
|
||||
export * from './index_pattern';
|
||||
export * from './kibana_react.mock';
|
||||
export * from './mock_and_providers';
|
||||
export * from './mock_data_providers';
|
||||
export * from './mock_timeline_control_columns';
|
||||
|
|
|
@ -13,8 +13,27 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p
|
|||
import { EuiTheme } from '../../../../../src/plugins/kibana_react/common';
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
|
||||
export const createStartServicesMock = (): CoreStart =>
|
||||
(coreMock.createStart() as unknown) as CoreStart;
|
||||
export const mockGetAllCasesSelectorModal = jest.fn();
|
||||
export const mockNavigateToApp = jest.fn();
|
||||
|
||||
export const createStartServicesMock = (): CoreStart => {
|
||||
const coreServices = coreMock.createStart();
|
||||
return ({
|
||||
...coreServices,
|
||||
cases: {
|
||||
getAllCases: jest.fn(),
|
||||
getCaseView: jest.fn(),
|
||||
getConfigureCases: jest.fn(),
|
||||
getCreateCase: jest.fn(),
|
||||
getRecentCases: jest.fn(),
|
||||
getAllCasesSelectorModal: mockGetAllCasesSelectorModal,
|
||||
},
|
||||
application: {
|
||||
...coreServices.application,
|
||||
navigateToApp: mockNavigateToApp,
|
||||
},
|
||||
} as unknown) as CoreStart;
|
||||
};
|
||||
|
||||
export const createWithKibanaMock = () => {
|
||||
const services = createStartServicesMock();
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { IS_OPERATOR } from '../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { DataProvider, DataProvidersAnd } from '../../common/types/timeline';
|
||||
|
||||
export const providerA: DataProvidersAnd = {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { IS_OPERATOR } from '../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { DataProvider } from '../../common/types/timeline';
|
||||
|
||||
interface NameToEventCount<TValue> {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import type { Ecs } from '../../common/ecs';
|
||||
import type { TimelineItem } from '../../common/search_strategy';
|
||||
import { TimelineItem, Direction } from '../../common/search_strategy';
|
||||
import type { TGridModel } from '../store/t_grid/model';
|
||||
|
||||
export const mockTimelineData: TimelineItem[] = [
|
||||
{
|
||||
|
@ -1509,3 +1510,76 @@ export const mockEndpointRegistryModificationEvent: Ecs = {
|
|||
timestamp: '2021-02-04T13:44:31.559Z',
|
||||
_id: '4cxLbXcBGrBB52F2uOfF',
|
||||
};
|
||||
|
||||
export const mockTgridModel: TGridModel = {
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: '@timestamp',
|
||||
initialWidth: 190,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'message',
|
||||
initialWidth: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'event.category',
|
||||
initialWidth: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'host.name',
|
||||
initialWidth: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'source.ip',
|
||||
initialWidth: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'destination.ip',
|
||||
initialWidth: 180,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
id: 'user.name',
|
||||
initialWidth: 180,
|
||||
},
|
||||
],
|
||||
defaultColumns: [],
|
||||
queryFields: [],
|
||||
dateRange: {
|
||||
end: '2020-03-18T13:52:38.929Z',
|
||||
start: '2020-03-18T13:46:38.929Z',
|
||||
},
|
||||
deletedEventIds: [],
|
||||
excludedRowRendererIds: [],
|
||||
expandedDetail: {},
|
||||
documentType: '',
|
||||
selectAll: false,
|
||||
id: 'ef579e40-jibber-jabber',
|
||||
indexNames: [],
|
||||
isLoading: false,
|
||||
isSelectAllChecked: false,
|
||||
kqlQuery: {
|
||||
filterQuery: null,
|
||||
},
|
||||
itemsPerPage: 25,
|
||||
itemsPerPageOptions: [10, 25, 50, 100],
|
||||
loadingEventIds: [],
|
||||
savedObjectId: 'ef579e40-jibber-jabber',
|
||||
selectedEventIds: {},
|
||||
showCheckboxes: false,
|
||||
sort: [
|
||||
{
|
||||
columnId: '@timestamp',
|
||||
columnType: 'number',
|
||||
sortDirection: Direction.desc,
|
||||
},
|
||||
],
|
||||
title: 'Test rule',
|
||||
version: '1',
|
||||
};
|
||||
|
|
|
@ -8,21 +8,21 @@
|
|||
import { Store } from 'redux';
|
||||
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import type { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import type {
|
||||
CoreSetup,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
CoreStart,
|
||||
} from '../../../../src/core/public';
|
||||
import type { TimelinesUIStart, TGridProps } from './types';
|
||||
import type { LastUpdatedAtProps, LoadingPanelProps, FieldBrowserWrappedProps } from './components';
|
||||
import {
|
||||
getLastUpdatedLazy,
|
||||
getLoadingPanelLazy,
|
||||
getTGridLazy,
|
||||
getFieldsBrowserLazy,
|
||||
getAddToCaseLazy,
|
||||
} from './methods';
|
||||
import type { TimelinesUIStart, TGridProps, TimelinesStartPlugins } from './types';
|
||||
import { tGridReducer } from './store/t_grid/reducer';
|
||||
import { useDraggableKeyboardWrapper } from './components/drag_and_drop/draggable_keyboard_wrapper_hook';
|
||||
import { useAddToTimeline, useAddToTimelineSensor } from './hooks/use_add_to_timeline';
|
||||
|
@ -34,7 +34,7 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
|
|||
|
||||
public setup(core: CoreSetup) {}
|
||||
|
||||
public start(core: CoreStart, { data }: { data: DataPublicPluginStart }): TimelinesUIStart {
|
||||
public start(core: CoreStart, { data }: TimelinesStartPlugins): TimelinesUIStart {
|
||||
const config = this.initializerContext.config.get<{ enabled: boolean }>();
|
||||
if (!config.enabled) {
|
||||
return {} as TimelinesUIStart;
|
||||
|
@ -77,6 +77,9 @@ export class TimelinesPlugin implements Plugin<void, TimelinesUIStart> {
|
|||
setTGridEmbeddedStore: (store: Store) => {
|
||||
this.setStore(store);
|
||||
},
|
||||
getAddToCaseAction: (props) => {
|
||||
return getAddToCaseLazy(props);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import type {
|
|||
SortColumnTimeline,
|
||||
TimelineExpandedDetailType,
|
||||
} from '../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import { TimelineTabs } from '../../../common/types/timeline';
|
||||
import { InitialyzeTGridSettings, TGridPersistInput } from './types';
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import type {
|
|||
SortColumnTimeline,
|
||||
SerializedFilterQuery,
|
||||
} from '../../../common/types/timeline';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import { RowRendererId } from '../../../common/types/timeline';
|
||||
|
||||
export interface TGridModelSettings {
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
import { ReactElement } from 'react';
|
||||
import type { SensorAPI } from 'react-beautiful-dnd';
|
||||
import { Store } from 'redux';
|
||||
import { CoreStart } from '../../../../src/core/public';
|
||||
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { CasesUiStart } from '../../cases/public';
|
||||
import type {
|
||||
LastUpdatedAtProps,
|
||||
LoadingPanelProps,
|
||||
|
@ -19,6 +22,7 @@ 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';
|
||||
export * from './store/t_grid';
|
||||
export interface TimelinesUIStart {
|
||||
getHoverActions: () => HoverActionsConfig;
|
||||
|
@ -36,7 +40,15 @@ export interface TimelinesUIStart {
|
|||
props: UseDraggableKeyboardWrapperProps
|
||||
) => UseDraggableKeyboardWrapper;
|
||||
setTGridEmbeddedStore: (store: Store) => void;
|
||||
getAddToCaseAction: (props: AddToCaseActionProps) => ReactElement<AddToCaseActionProps>;
|
||||
}
|
||||
|
||||
export interface TimelinesStartPlugins {
|
||||
data: DataPublicPluginStart;
|
||||
cases: CasesUiStart;
|
||||
}
|
||||
|
||||
export type TimelinesStartServices = CoreStart & TimelinesStartPlugins;
|
||||
interface TGridStandaloneCompProps extends TGridStandaloneProps {
|
||||
type: 'standalone';
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
{ "path": "../../../src/plugins/home/tsconfig.json" },
|
||||
{ "path": "../data_enhanced/tsconfig.json" },
|
||||
{ "path": "../features/tsconfig.json" },
|
||||
{ "path": "../cases/tsconfig.json" },
|
||||
{ "path": "../licensing/tsconfig.json" },
|
||||
{ "path": "../spaces/tsconfig.json" },
|
||||
{ "path": "../alerting/tsconfig.json" }
|
||||
|
|
|
@ -20358,16 +20358,6 @@
|
|||
"xpack.securitySolution.cases.createCase.titleFieldRequiredError": "タイトルが必要です。",
|
||||
"xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle": "閉じる",
|
||||
"xpack.securitySolution.cases.pageTitle": "ケース",
|
||||
"xpack.securitySolution.cases.timeline.actions.addCase": "ケースに追加",
|
||||
"xpack.securitySolution.cases.timeline.actions.addExistingCase": "既存のケースに追加",
|
||||
"xpack.securitySolution.cases.timeline.actions.addNewCase": "新しいケースに追加",
|
||||
"xpack.securitySolution.cases.timeline.actions.addToCaseAriaLabel": "アラートをケースに関連付ける",
|
||||
"xpack.securitySolution.cases.timeline.actions.addToCaseTooltip": "ケースに追加",
|
||||
"xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast": "アラートが「{title}」に追加されました",
|
||||
"xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText": "このケースのアラートはステータスがケースステータスと同期されました",
|
||||
"xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "ケースの表示",
|
||||
"xpack.securitySolution.cases.timeline.actions.permissionsMessage": "現在、アラートをケースに関連付けるための必要な権限がありません。サポートについては、管理者にお問い合わせください。",
|
||||
"xpack.securitySolution.cases.timeline.actions.unsupportedEventsMessage": "このイベントはケースに関連付けられません",
|
||||
"xpack.securitySolution.certificate.fingerprint.clientCertLabel": "クライアント証明書",
|
||||
"xpack.securitySolution.certificate.fingerprint.serverCertLabel": "サーバー証明書",
|
||||
"xpack.securitySolution.chart.allOthersGroupingLabel": "その他すべて",
|
||||
|
|
|
@ -20638,16 +20638,6 @@
|
|||
"xpack.securitySolution.cases.createCase.titleFieldRequiredError": "标题必填。",
|
||||
"xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle": "关闭",
|
||||
"xpack.securitySolution.cases.pageTitle": "案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.addCase": "添加到案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.addExistingCase": "添加到现有案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.addNewCase": "添加到新案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.addToCaseAriaLabel": "将告警附加到案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.addToCaseTooltip": "添加到案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast": "告警已添加到“{title}”",
|
||||
"xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText": "此案例中的告警的状态已经与案例状态同步",
|
||||
"xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "查看案例",
|
||||
"xpack.securitySolution.cases.timeline.actions.permissionsMessage": "您当前缺少所需的权限,无法向案例附加告警。有关进一步帮助,请联系您的管理员。",
|
||||
"xpack.securitySolution.cases.timeline.actions.unsupportedEventsMessage": "此事件无法附加到案例",
|
||||
"xpack.securitySolution.certificate.fingerprint.clientCertLabel": "客户端证书",
|
||||
"xpack.securitySolution.certificate.fingerprint.serverCertLabel": "服务器证书",
|
||||
"xpack.securitySolution.chart.allOthersGroupingLabel": "所有其他",
|
||||
|
|
Loading…
Reference in a new issue