mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] expandable flyout - add loading and no data states to Investigation guide in right and left panels (#164876)
This commit is contained in:
parent
225fc95488
commit
dc8971d2c9
21 changed files with 527 additions and 239 deletions
|
@ -171,8 +171,8 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
const goToTableTab = useCallback(() => setSelectedTabId(EventsViewType.tableView), []);
|
||||
|
||||
const eventFields = useMemo(() => getEnrichmentFields(data), [data]);
|
||||
const { ruleId } = useBasicDataFromDetailsData(data);
|
||||
const { rule: maybeRule } = useRuleWithFallback(ruleId);
|
||||
const basicAlertData = useBasicDataFromDetailsData(data);
|
||||
const { rule: maybeRule } = useRuleWithFallback(basicAlertData.ruleId);
|
||||
const existingEnrichments = useMemo(
|
||||
() =>
|
||||
isAlert
|
||||
|
@ -222,6 +222,7 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
const endpointResponseActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'endpointResponseActionsEnabled'
|
||||
);
|
||||
|
||||
const summaryTab: EventViewTab | undefined = useMemo(
|
||||
() =>
|
||||
isAlert
|
||||
|
@ -319,7 +320,9 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
</>
|
||||
)}
|
||||
|
||||
<InvestigationGuideView data={data} />
|
||||
{basicAlertData.ruleId && maybeRule?.note && (
|
||||
<InvestigationGuideView basicData={basicAlertData} ruleNote={maybeRule.note} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
}
|
||||
|
@ -332,17 +335,19 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
id,
|
||||
handleOnEventClosed,
|
||||
isReadOnly,
|
||||
threatDetails,
|
||||
renderer,
|
||||
detailsEcsData,
|
||||
isDraggable,
|
||||
goToTableTab,
|
||||
threatDetails,
|
||||
maybeRule?.investigation_fields,
|
||||
maybeRule?.note,
|
||||
showThreatSummary,
|
||||
hostRisk,
|
||||
userRisk,
|
||||
allEnrichments,
|
||||
isEnrichmentsLoading,
|
||||
maybeRule,
|
||||
basicAlertData,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -8,27 +8,17 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { InvestigationGuideView } from './investigation_guide_view';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
|
||||
const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock;
|
||||
jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback');
|
||||
import type { GetBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
|
||||
const defaultProps = {
|
||||
data: [
|
||||
{
|
||||
category: 'kibana',
|
||||
field: 'kibana.alert.rule.uuid',
|
||||
values: ['rule-uuid'],
|
||||
originalValue: ['rule-uuid'],
|
||||
isObjectArray: false,
|
||||
},
|
||||
],
|
||||
basicData: {
|
||||
ruleId: 'rule-id',
|
||||
} as unknown as GetBasicDataFromDetailsData,
|
||||
ruleNote: 'test note',
|
||||
};
|
||||
|
||||
describe('Investigation guide view', () => {
|
||||
it('should render title and clamped investigation guide (with read more/read less) by default', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({ rule: { note: 'test note' } });
|
||||
|
||||
const { getByTestId, queryByTestId } = render(<InvestigationGuideView {...defaultProps} />);
|
||||
|
||||
expect(getByTestId('summary-view-guide')).toBeInTheDocument();
|
||||
|
@ -37,8 +27,6 @@ describe('Investigation guide view', () => {
|
|||
});
|
||||
|
||||
it('should render full investigation guide when showFullView is true', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({ rule: { note: 'test note' } });
|
||||
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<InvestigationGuideView {...defaultProps} showFullView={true} />
|
||||
);
|
||||
|
@ -47,19 +35,6 @@ describe('Investigation guide view', () => {
|
|||
expect(queryByTestId('investigation-guide-clamped')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render investigation guide when rule id is not available', () => {
|
||||
const { queryByTestId } = render(<InvestigationGuideView {...defaultProps} data={[]} />);
|
||||
expect(queryByTestId('investigation-guide-clamped')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('investigation-guide-full-view')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render investigation guide button when investigation guide is not available', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({ rule: {} });
|
||||
const { queryByTestId } = render(<InvestigationGuideView {...defaultProps} />);
|
||||
expect(queryByTestId('investigation-guide-clamped')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('investigation-guide-full-view')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render investigation guide title when showTitle is false', () => {
|
||||
const { queryByTestId } = render(
|
||||
<InvestigationGuideView {...defaultProps} showTitle={false} />
|
||||
|
|
|
@ -9,12 +9,9 @@ import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
|
|||
import React, { createContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { GetBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import * as i18n from './translations';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
import { MarkdownRenderer } from '../markdown_editor';
|
||||
import { LineClamp } from '../line_clamp';
|
||||
import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
|
||||
|
||||
export const Indent = styled.div`
|
||||
padding: 0 8px;
|
||||
|
@ -25,9 +22,13 @@ export const BasicAlertDataContext = createContext<Partial<GetBasicDataFromDetai
|
|||
|
||||
interface InvestigationGuideViewProps {
|
||||
/**
|
||||
* An array of events data
|
||||
* An object of basic fields from the event details data
|
||||
*/
|
||||
data: TimelineEventsDetailsItem[];
|
||||
basicData: GetBasicDataFromDetailsData;
|
||||
/**
|
||||
* The markdown text of rule.note
|
||||
*/
|
||||
ruleNote: string;
|
||||
/**
|
||||
* Boolean value indicating whether to show the full view of investigation guide, defaults to false and shows partial text
|
||||
* with Read more button
|
||||
|
@ -43,19 +44,13 @@ interface InvestigationGuideViewProps {
|
|||
* Investigation guide that shows the markdown text of rule.note
|
||||
*/
|
||||
const InvestigationGuideViewComponent: React.FC<InvestigationGuideViewProps> = ({
|
||||
data,
|
||||
basicData,
|
||||
ruleNote,
|
||||
showFullView = false,
|
||||
showTitle = true,
|
||||
}) => {
|
||||
const basicAlertData = useBasicDataFromDetailsData(data);
|
||||
const { rule: maybeRule } = useRuleWithFallback(basicAlertData.ruleId);
|
||||
|
||||
if (!basicAlertData.ruleId || !maybeRule?.note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BasicAlertDataContext.Provider value={basicAlertData}>
|
||||
<BasicAlertDataContext.Provider value={basicData}>
|
||||
{showTitle && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
|
@ -68,12 +63,12 @@ const InvestigationGuideViewComponent: React.FC<InvestigationGuideViewProps> = (
|
|||
<Indent>
|
||||
{showFullView ? (
|
||||
<EuiText size="xs" data-test-subj="investigation-guide-full-view">
|
||||
<MarkdownRenderer>{maybeRule.note}</MarkdownRenderer>
|
||||
<MarkdownRenderer>{ruleNote}</MarkdownRenderer>
|
||||
</EuiText>
|
||||
) : (
|
||||
<EuiText size="xs" data-test-subj="investigation-guide-clamped">
|
||||
<LineClamp lineClampHeight={4.5}>
|
||||
<MarkdownRenderer>{maybeRule.note}</MarkdownRenderer>
|
||||
<MarkdownRenderer>{ruleNote}</MarkdownRenderer>
|
||||
</LineClamp>
|
||||
</EuiText>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { InvestigationGuide } from './investigation_guide';
|
||||
import { LeftPanelContext } from '../context';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import {
|
||||
INVESTIGATION_GUIDE_LOADING_TEST_ID,
|
||||
INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { mockContextValue } from '../mocks/mock_context';
|
||||
import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide';
|
||||
|
||||
jest.mock('../../shared/hooks/use_investigation_guide');
|
||||
|
||||
const renderInvestigationGuide = (context: LeftPanelContext = mockContextValue) => (
|
||||
<TestProviders>
|
||||
<LeftPanelContext.Provider value={context}>
|
||||
<InvestigationGuide />
|
||||
</LeftPanelContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
describe('<InvestigationGuide />', () => {
|
||||
it('should render investigation guide', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
basicAlertData: { ruleId: 'ruleId' },
|
||||
ruleNote: 'test note',
|
||||
});
|
||||
const { queryByTestId } = render(renderInvestigationGuide());
|
||||
expect(queryByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render loading', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: true,
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render no data message when there is no ruleId', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
basicAlertData: {},
|
||||
ruleNote: 'test note',
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render no data message when there is no rule note', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
basicAlertData: { ruleId: 'ruleId' },
|
||||
ruleNote: undefined,
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render null when dataFormattedForFieldBrowser is null', () => {
|
||||
const mockContext = {
|
||||
...mockContextValue,
|
||||
dataFormattedForFieldBrowser: null,
|
||||
};
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
const { container } = render(renderInvestigationGuide(mockContext));
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render null useInvestigationGuide errors out', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: true,
|
||||
});
|
||||
const { container } = render(renderInvestigationGuide());
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiLink, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide';
|
||||
import { useLeftPanelContext } from '../context';
|
||||
import {
|
||||
INVESTIGATION_GUIDE_LOADING_TEST_ID,
|
||||
INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { InvestigationGuideView } from '../../../common/components/event_details/investigation_guide_view';
|
||||
|
||||
/**
|
||||
* Investigation guide displayed in the left panel.
|
||||
* Renders a message saying the guide hasn't been set up or the full investigation guide.
|
||||
*/
|
||||
export const InvestigationGuide: React.FC = () => {
|
||||
const { dataFormattedForFieldBrowser } = useLeftPanelContext();
|
||||
|
||||
const { loading, error, basicAlertData, ruleNote } = useInvestigationGuide({
|
||||
dataFormattedForFieldBrowser,
|
||||
});
|
||||
|
||||
if (!dataFormattedForFieldBrowser || error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
data-test-subj={INVESTIGATION_GUIDE_LOADING_TEST_ID}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{basicAlertData.ruleId && ruleNote ? (
|
||||
<InvestigationGuideView
|
||||
basicData={basicAlertData}
|
||||
ruleNote={ruleNote}
|
||||
showTitle={false}
|
||||
showFullView={true}
|
||||
/>
|
||||
) : (
|
||||
<div data-test-subj={INVESTIGATION_GUIDE_NO_DATA_TEST_ID}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.investigationGuideNoData"
|
||||
defaultMessage="An investigation guide has not been created for this rule. Refer to this {documentation} to learn more about adding investigation guides."
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/guide/en/security/current/rules-ui-create.html#rule-ui-advanced-params"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.documentDetails.investigationGuideDocumentationLink"
|
||||
defaultMessage="documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
InvestigationGuide.displayName = 'InvestigationGuide';
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
/* Visualization tab */
|
||||
|
||||
const PREFIX = 'securitySolutionDocumentDetailsFlyout' as const;
|
||||
|
||||
export const ANALYZER_GRAPH_TEST_ID = `${PREFIX}AnalyzerGraph` as const;
|
||||
|
@ -75,3 +76,6 @@ export const CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID =
|
|||
export const RESPONSE_BASE_TEST_ID = `${PREFIX}Responses` as const;
|
||||
export const RESPONSE_DETAILS_TEST_ID = `${RESPONSE_BASE_TEST_ID}Details` as const;
|
||||
export const RESPONSE_EMPTY_TEST_ID = `${RESPONSE_BASE_TEST_ID}Empty` as const;
|
||||
|
||||
export const INVESTIGATION_GUIDE_LOADING_TEST_ID = `${PREFIX}InvestigationGuideLoading`;
|
||||
export const INVESTIGATION_GUIDE_NO_DATA_TEST_ID = `${PREFIX}NoData`;
|
||||
|
|
|
@ -197,27 +197,3 @@ export const CORRELATIONS_CASE_NAME_COLUMN_TITLE = i18n.translate(
|
|||
defaultMessage: 'Name',
|
||||
}
|
||||
);
|
||||
|
||||
export const ANCESTRY_ALERTS_HEADING = (count: number) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.correlations.ancestryAlertsHeading', {
|
||||
defaultMessage: '{count, plural, one {# alert} other {# alerts}} related by ancestry',
|
||||
values: { count },
|
||||
});
|
||||
|
||||
export const SOURCE_ALERTS_HEADING = (count: number) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.correlations.sourceAlertsHeading', {
|
||||
defaultMessage: '{count, plural, one {# alert} other {# alerts}} related by source event',
|
||||
values: { count },
|
||||
});
|
||||
|
||||
export const SESSION_ALERTS_HEADING = (count: number) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.correlations.sessionAlertsHeading', {
|
||||
defaultMessage: '{count, plural, one {# alert} other {# alerts}} related by session',
|
||||
values: { count },
|
||||
});
|
||||
|
||||
export const RELATED_CASES_HEADING = (count: number) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.correlations.relatedCasesHeading', {
|
||||
defaultMessage: '{count} related {count, plural, one {case} other {cases}}',
|
||||
values: { count },
|
||||
});
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { InvestigationGuide } from '../components/investigation_guide';
|
||||
import { INVESTIGATION_TAB_CONTENT_TEST_ID } from './test_ids';
|
||||
import { InvestigationGuideView } from '../../../common/components/event_details/investigation_guide_view';
|
||||
import { useLeftPanelContext } from '../context';
|
||||
|
||||
/**
|
||||
|
@ -16,17 +16,13 @@ import { useLeftPanelContext } from '../context';
|
|||
*/
|
||||
export const InvestigationTab: React.FC = memo(() => {
|
||||
const { dataFormattedForFieldBrowser } = useLeftPanelContext();
|
||||
if (dataFormattedForFieldBrowser === null) {
|
||||
if (dataFormattedForFieldBrowser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPanel data-test-subj={INVESTIGATION_TAB_CONTENT_TEST_ID} hasShadow={false}>
|
||||
<InvestigationGuideView
|
||||
data={dataFormattedForFieldBrowser}
|
||||
showTitle={false}
|
||||
showFullView={true}
|
||||
/>
|
||||
<InvestigationGuide />
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { InvestigationGuide } from './investigation_guide';
|
||||
import { RightPanelContext } from '../context';
|
||||
import {
|
||||
INVESTIGATION_GUIDE_BUTTON_TEST_ID,
|
||||
INVESTIGATION_GUIDE_LOADING_TEST_ID,
|
||||
INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { mockContextValue } from '../mocks/mock_right_panel_context';
|
||||
import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context';
|
||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide';
|
||||
|
||||
jest.mock('../../shared/hooks/use_investigation_guide');
|
||||
|
||||
const renderInvestigationGuide = (context: RightPanelContext = mockContextValue) => (
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RightPanelContext.Provider value={context}>
|
||||
<InvestigationGuide />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
|
||||
describe('<InvestigationGuide />', () => {
|
||||
it('should render investigation guide button correctly', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
basicAlertData: { ruleId: 'ruleId' },
|
||||
ruleNote: 'test note',
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render loading', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: true,
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render no data message when there is no ruleId', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
basicAlertData: {},
|
||||
ruleNote: 'test note',
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render no data message when there is no rule note', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
basicAlertData: { ruleId: 'ruleId' },
|
||||
ruleNote: undefined,
|
||||
});
|
||||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render investigation guide button when dataFormattedForFieldBrowser is null', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
show: false,
|
||||
});
|
||||
const mockContext = {
|
||||
...mockContextValue,
|
||||
dataFormattedForFieldBrowser: null,
|
||||
};
|
||||
const { container } = render(renderInvestigationGuide(mockContext));
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render null when useInvestigationGuide errors out', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: true,
|
||||
show: false,
|
||||
});
|
||||
|
||||
const { container } = render(renderInvestigationGuide());
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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, { useCallback } from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiTitle } from '@elastic/eui';
|
||||
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
|
||||
import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide';
|
||||
import { useRightPanelContext } from '../context';
|
||||
import { LeftPanelKey, LeftPanelInvestigationTab } from '../../left';
|
||||
import {
|
||||
INVESTIGATION_GUIDE_BUTTON_TEST_ID,
|
||||
INVESTIGATION_GUIDE_LOADING_TEST_ID,
|
||||
INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
|
||||
INVESTIGATION_GUIDE_TEST_ID,
|
||||
} from './test_ids';
|
||||
import {
|
||||
INVESTIGATION_GUIDE_BUTTON,
|
||||
INVESTIGATION_GUIDE_NO_DATA,
|
||||
INVESTIGATION_GUIDE_TITLE,
|
||||
} from './translations';
|
||||
|
||||
/**
|
||||
* Render either the investigation guide button that opens Investigation section in the left panel,
|
||||
* or a no-data message if investigation guide hasn't been set up on the rule
|
||||
*/
|
||||
export const InvestigationGuide: React.FC = () => {
|
||||
const { openLeftPanel } = useExpandableFlyoutContext();
|
||||
const { eventId, indexName, scopeId, dataFormattedForFieldBrowser } = useRightPanelContext();
|
||||
|
||||
const { loading, error, basicAlertData, ruleNote } = useInvestigationGuide({
|
||||
dataFormattedForFieldBrowser,
|
||||
});
|
||||
|
||||
const goToInvestigationsTab = useCallback(() => {
|
||||
openLeftPanel({
|
||||
id: LeftPanelKey,
|
||||
path: {
|
||||
tab: LeftPanelInvestigationTab,
|
||||
},
|
||||
params: {
|
||||
id: eventId,
|
||||
indexName,
|
||||
scopeId,
|
||||
},
|
||||
});
|
||||
}, [eventId, indexName, openLeftPanel, scopeId]);
|
||||
|
||||
if (!dataFormattedForFieldBrowser || error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
data-test-subj={INVESTIGATION_GUIDE_LOADING_TEST_ID}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem data-test-subj={INVESTIGATION_GUIDE_TEST_ID}>
|
||||
<EuiTitle size="xxs">
|
||||
<h5>{INVESTIGATION_GUIDE_TITLE}</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{basicAlertData.ruleId && ruleNote ? (
|
||||
<EuiButton
|
||||
onClick={goToInvestigationsTab}
|
||||
iconType="documentation"
|
||||
data-test-subj={INVESTIGATION_GUIDE_BUTTON_TEST_ID}
|
||||
>
|
||||
{INVESTIGATION_GUIDE_BUTTON}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<div data-test-subj={INVESTIGATION_GUIDE_NO_DATA_TEST_ID}>
|
||||
{INVESTIGATION_GUIDE_NO_DATA}
|
||||
</div>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
InvestigationGuide.displayName = 'InvestigationGuideButton';
|
|
@ -1,81 +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 { render } from '@testing-library/react';
|
||||
import { InvestigationGuideButton } from './investigation_guide_button';
|
||||
import { RightPanelContext } from '../context';
|
||||
import { INVESTIGATION_GUIDE_BUTTON_TEST_ID } from './test_ids';
|
||||
import { mockContextValue } from '../mocks/mock_right_panel_context';
|
||||
import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context';
|
||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
|
||||
const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock;
|
||||
jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback');
|
||||
|
||||
describe('<InvestigationGuideButton />', () => {
|
||||
it('should render investigation guide button correctly', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({ rule: { note: 'test note' } });
|
||||
const { getByTestId } = render(
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RightPanelContext.Provider value={mockContextValue}>
|
||||
<InvestigationGuideButton />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render investigation guide button when dataFormattedForFieldBrowser is null', () => {
|
||||
const { container } = render(
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RightPanelContext.Provider
|
||||
value={{ ...mockContextValue, dataFormattedForFieldBrowser: null }}
|
||||
>
|
||||
<InvestigationGuideButton />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should not render investigation guide button when rule id is null', () => {
|
||||
const { container } = render(
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RightPanelContext.Provider
|
||||
value={{
|
||||
...mockContextValue,
|
||||
dataFormattedForFieldBrowser: [
|
||||
{
|
||||
category: 'kibana',
|
||||
field: 'kibana.alert.rule.uuid',
|
||||
isObjectArray: false,
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<InvestigationGuideButton />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should not render investigation guide button when investigation guide is not available', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({ rule: {} });
|
||||
const { container } = render(
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RightPanelContext.Provider value={mockContextValue}>
|
||||
<InvestigationGuideButton />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
|
@ -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, { useCallback } from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
|
||||
import { useRightPanelContext } from '../context';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { LeftPanelKey, LeftPanelInvestigationTab } from '../../left';
|
||||
import { INVESTIGATION_GUIDE_BUTTON_TEST_ID } from './test_ids';
|
||||
import { INVESTIGATION_GUIDE_TITLE } from './translations';
|
||||
|
||||
/**
|
||||
* Investigation guide button that opens Investigation section in the left panel
|
||||
*/
|
||||
export const InvestigationGuideButton: React.FC = () => {
|
||||
const { openLeftPanel } = useExpandableFlyoutContext();
|
||||
const { eventId, indexName, scopeId, dataFormattedForFieldBrowser } = useRightPanelContext();
|
||||
|
||||
const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
|
||||
const { rule: maybeRule } = useRuleWithFallback(ruleId);
|
||||
|
||||
const goToInvestigationsTab = useCallback(() => {
|
||||
openLeftPanel({
|
||||
id: LeftPanelKey,
|
||||
path: {
|
||||
tab: LeftPanelInvestigationTab,
|
||||
},
|
||||
params: {
|
||||
id: eventId,
|
||||
indexName,
|
||||
scopeId,
|
||||
},
|
||||
});
|
||||
}, [eventId, indexName, openLeftPanel, scopeId]);
|
||||
|
||||
if (!dataFormattedForFieldBrowser || !ruleId || !maybeRule?.note) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiButton
|
||||
onClick={goToInvestigationsTab}
|
||||
iconType="documentation"
|
||||
data-test-subj={INVESTIGATION_GUIDE_BUTTON_TEST_ID}
|
||||
>
|
||||
{INVESTIGATION_GUIDE_TITLE}
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
||||
|
||||
InvestigationGuideButton.displayName = 'InvestigationGuideButton';
|
|
@ -12,7 +12,7 @@ import { ExpandableSection } from './expandable_section';
|
|||
import { HighlightedFields } from './highlighted_fields';
|
||||
import { INVESTIGATION_SECTION_TEST_ID } from './test_ids';
|
||||
import { INVESTIGATION_TITLE } from './translations';
|
||||
import { InvestigationGuideButton } from './investigation_guide_button';
|
||||
import { InvestigationGuide } from './investigation_guide';
|
||||
export interface DescriptionSectionProps {
|
||||
/**
|
||||
* Boolean to allow the component to be expanded or collapsed on first render
|
||||
|
@ -30,7 +30,7 @@ export const InvestigationSection: VFC<DescriptionSectionProps> = ({ expanded =
|
|||
title={INVESTIGATION_TITLE}
|
||||
data-test-subj={INVESTIGATION_SECTION_TEST_ID}
|
||||
>
|
||||
<InvestigationGuideButton />
|
||||
<InvestigationGuide />
|
||||
<EuiSpacer size="m" />
|
||||
<HighlightedFields />
|
||||
</ExpandableSection>
|
||||
|
|
|
@ -63,8 +63,11 @@ export const HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID =
|
|||
export const HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsAgentStatusCell';
|
||||
|
||||
export const INVESTIGATION_GUIDE_BUTTON_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutInvestigationGuideButton';
|
||||
export const INVESTIGATION_GUIDE_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutInvestigationGuide';
|
||||
export const INVESTIGATION_GUIDE_BUTTON_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Button`;
|
||||
export const INVESTIGATION_GUIDE_LOADING_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Loading`;
|
||||
export const INVESTIGATION_GUIDE_NO_DATA_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}NoData`;
|
||||
|
||||
/* Insights section */
|
||||
|
||||
|
|
|
@ -219,12 +219,26 @@ export const SHARE = i18n.translate('xpack.securitySolution.flyout.documentDetai
|
|||
});
|
||||
|
||||
export const INVESTIGATION_GUIDE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.investigationGuideText',
|
||||
'xpack.securitySolution.flyout.documentDetails.investigationGuideTitle',
|
||||
{
|
||||
defaultMessage: 'Investigation guide',
|
||||
}
|
||||
);
|
||||
|
||||
export const INVESTIGATION_GUIDE_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.investigationGuideButton',
|
||||
{
|
||||
defaultMessage: 'Show investigation guide',
|
||||
}
|
||||
);
|
||||
|
||||
export const INVESTIGATION_GUIDE_NO_DATA = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.investigationGuideNoData',
|
||||
{
|
||||
defaultMessage: 'An investigation guide has not been created for this rule.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SESSION_PREVIEW_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.sessionPreview.title',
|
||||
{
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 type { RenderHookResult } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import type {
|
||||
UseInvestigationGuideParams,
|
||||
UseInvestigationGuideResult,
|
||||
} from './use_investigation_guide';
|
||||
import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context';
|
||||
import { useInvestigationGuide } from './use_investigation_guide';
|
||||
|
||||
jest.mock('../../../timelines/components/side_panel/event_details/helpers');
|
||||
jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback');
|
||||
|
||||
const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser;
|
||||
|
||||
describe('useInvestigationGuide', () => {
|
||||
let hookResult: RenderHookResult<UseInvestigationGuideParams, UseInvestigationGuideResult>;
|
||||
|
||||
it('should return loading true', () => {
|
||||
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ ruleId: 'ruleId' });
|
||||
(useRuleWithFallback as jest.Mock).mockReturnValue({ loading: true });
|
||||
|
||||
hookResult = renderHook(() => useInvestigationGuide({ dataFormattedForFieldBrowser }));
|
||||
|
||||
expect(hookResult.result.current.loading).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return error true', () => {
|
||||
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ ruleId: 'ruleId' });
|
||||
(useRuleWithFallback as jest.Mock).mockReturnValue({ error: true });
|
||||
|
||||
hookResult = renderHook(() => useInvestigationGuide({ dataFormattedForFieldBrowser }));
|
||||
});
|
||||
|
||||
it('should return basicAlertData and ruleNote', () => {
|
||||
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ ruleId: 'ruleId' });
|
||||
(useRuleWithFallback as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
basicAlertsData: { ruleId: 'ruleId' },
|
||||
rule: { note: 'test note' },
|
||||
});
|
||||
|
||||
hookResult = renderHook(() => useInvestigationGuide({ dataFormattedForFieldBrowser }));
|
||||
|
||||
expect(hookResult.result.current.loading).toBeFalsy();
|
||||
expect(hookResult.result.current.error).toBeFalsy();
|
||||
expect(hookResult.result.current.basicAlertData).toEqual({ ruleId: 'ruleId' });
|
||||
expect(hookResult.result.current.ruleNote).toEqual('test note');
|
||||
});
|
||||
});
|
|
@ -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 type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import type { GetBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
|
||||
export interface UseInvestigationGuideParams {
|
||||
/**
|
||||
* An array of field objects with category and value
|
||||
*/
|
||||
dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null;
|
||||
}
|
||||
|
||||
export interface UseInvestigationGuideResult {
|
||||
/**
|
||||
* True if investigation guide data is loading
|
||||
*/
|
||||
loading: boolean;
|
||||
/**
|
||||
* True if investigation guide data is in error state
|
||||
*/
|
||||
error: unknown;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
basicAlertData: GetBasicDataFromDetailsData;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ruleNote: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the investigation guide data for a given rule is available to render
|
||||
*/
|
||||
export const useInvestigationGuide = ({
|
||||
dataFormattedForFieldBrowser,
|
||||
}: UseInvestigationGuideParams): UseInvestigationGuideResult => {
|
||||
const basicAlertData = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
|
||||
const { loading, error, rule: maybeRule } = useRuleWithFallback(basicAlertData.ruleId);
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
basicAlertData,
|
||||
ruleNote: maybeRule?.note,
|
||||
};
|
||||
};
|
|
@ -30012,10 +30012,6 @@
|
|||
"xpack.securitySolution.exceptions.viewer.lastUpdated": "Mis à jour {updated}",
|
||||
"xpack.securitySolution.exceptions.viewer.paginationDetails": "Affichage de {partOne} sur {partTwo}",
|
||||
"xpack.securitySolution.fieldBrowser.descriptionForScreenReaderOnly": "Description pour le champ {field} :",
|
||||
"xpack.securitySolution.flyout.correlations.ancestryAlertsHeading": "{count, plural, one {# alerte} many {# alertes} other {Alertes #}} associé par ancêtre",
|
||||
"xpack.securitySolution.flyout.correlations.relatedCasesHeading": "{count} associé {count, plural, one {cas} many {aux cas suivants} other {aux cas suivants}}",
|
||||
"xpack.securitySolution.flyout.correlations.sessionAlertsHeading": "{count, plural, one {# alerte} many {# alertes} other {Alertes #}} associé(es) par session",
|
||||
"xpack.securitySolution.flyout.correlations.sourceAlertsHeading": "{count, plural, one {# alerte} many {# alertes} other {Alertes #}} associé(es) par événement source",
|
||||
"xpack.securitySolution.flyout.errorMessage": "Une erreur est survenue lors de l'affichage de {message}",
|
||||
"xpack.securitySolution.flyout.errorTitle": "Impossible d'afficher {title}",
|
||||
"xpack.securitySolution.footer.autoRefreshActiveTooltip": "Lorsque le rafraîchissement automatique est activé, la chronologie vous montre les {numberOfItems} derniers événements qui correspondent à votre requête.",
|
||||
|
@ -33295,7 +33291,6 @@
|
|||
"xpack.securitySolution.flyout.documentDetails.insightsOptions": "Options des informations exploitables",
|
||||
"xpack.securitySolution.flyout.documentDetails.insightsTab": "Informations exploitables",
|
||||
"xpack.securitySolution.flyout.documentDetails.insightsTitle": "Informations exploitables",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationGuideText": "Guide d'investigation",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationSectionTitle": "Investigation",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationsTab": "Investigation",
|
||||
"xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON",
|
||||
|
|
|
@ -30011,10 +30011,6 @@
|
|||
"xpack.securitySolution.exceptions.viewer.lastUpdated": "{updated}を更新しました",
|
||||
"xpack.securitySolution.exceptions.viewer.paginationDetails": "{partOne}/{partTwo}ページを表示中",
|
||||
"xpack.securitySolution.fieldBrowser.descriptionForScreenReaderOnly": "フィールド{field}の説明:",
|
||||
"xpack.securitySolution.flyout.correlations.ancestryAlertsHeading": "上位項目に関連する{count, plural, other {#件のアラート}}",
|
||||
"xpack.securitySolution.flyout.correlations.relatedCasesHeading": "{count}件の関連する{count, plural, other {ケース}}",
|
||||
"xpack.securitySolution.flyout.correlations.sessionAlertsHeading": "セッションに関連する{count, plural, other {#件のアラート}}",
|
||||
"xpack.securitySolution.flyout.correlations.sourceAlertsHeading": "ソースイベントに関連する{count, plural, other {#件のアラート}}",
|
||||
"xpack.securitySolution.flyout.errorMessage": "{message}の表示中にエラーが発生しました",
|
||||
"xpack.securitySolution.flyout.errorTitle": "{title}を表示できません",
|
||||
"xpack.securitySolution.footer.autoRefreshActiveTooltip": "自動更新が有効な間、タイムラインはクエリに一致する直近{numberOfItems}件のイベントを表示します。",
|
||||
|
@ -33294,7 +33290,6 @@
|
|||
"xpack.securitySolution.flyout.documentDetails.insightsOptions": "インサイトオプション",
|
||||
"xpack.securitySolution.flyout.documentDetails.insightsTab": "インサイト",
|
||||
"xpack.securitySolution.flyout.documentDetails.insightsTitle": "インサイト",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationGuideText": "調査ガイド",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationSectionTitle": "調査",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationsTab": "調査",
|
||||
"xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON",
|
||||
|
|
|
@ -30007,10 +30007,6 @@
|
|||
"xpack.securitySolution.exceptions.viewer.lastUpdated": "已更新 {updated}",
|
||||
"xpack.securitySolution.exceptions.viewer.paginationDetails": "正在显示 {partOne} 个,共 {partTwo} 个",
|
||||
"xpack.securitySolution.fieldBrowser.descriptionForScreenReaderOnly": "{field} 字段的描述:",
|
||||
"xpack.securitySolution.flyout.correlations.ancestryAlertsHeading": "{count, plural, other {# 个告警}}与体系相关",
|
||||
"xpack.securitySolution.flyout.correlations.relatedCasesHeading": "{count} 个相关{count, plural, other {案例}}",
|
||||
"xpack.securitySolution.flyout.correlations.sessionAlertsHeading": "{count, plural, other {# 个告警}}与会话相关",
|
||||
"xpack.securitySolution.flyout.correlations.sourceAlertsHeading": "{count, plural, other {# 个告警}}与源事件相关",
|
||||
"xpack.securitySolution.flyout.errorMessage": "显示 {message} 时出现错误",
|
||||
"xpack.securitySolution.flyout.errorTitle": "无法显示 {title}",
|
||||
"xpack.securitySolution.footer.autoRefreshActiveTooltip": "自动刷新已启用时,时间线将显示匹配查询的最近 {numberOfItems} 个事件。",
|
||||
|
@ -33290,7 +33286,6 @@
|
|||
"xpack.securitySolution.flyout.documentDetails.insightsOptions": "洞见选项",
|
||||
"xpack.securitySolution.flyout.documentDetails.insightsTab": "洞见",
|
||||
"xpack.securitySolution.flyout.documentDetails.insightsTitle": "洞见",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationGuideText": "调查指南",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationSectionTitle": "调查",
|
||||
"xpack.securitySolution.flyout.documentDetails.investigationsTab": "调查",
|
||||
"xpack.securitySolution.flyout.documentDetails.jsonTab": "JSON",
|
||||
|
|
|
@ -169,7 +169,7 @@ describe(
|
|||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Investigation guide');
|
||||
.and('have.text', 'Show investigation guide');
|
||||
|
||||
cy.log('should navigate to left Investigation tab');
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue