mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Security Solution] Expandable flyout - Rule preview contents (#163027)
## Summary
This PR is part 2 of adding a rule preview panel to the expandable
flyout. PR (https://github.com/elastic/kibana/pull/161999) adds the
preview skeleton, and this PR populates the actual content related to
rule details:
Expandable flyout:
- Updated title to include `created by` and `updated by` timestamps, and
rule switch button
- Added contents for about, define, schedule and actions (if any)
- Added a hook to fetch data for rule switch button - logic mimics rule
details page
(`~/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx`)
Rule & detections:
- Added `isPanelView` option allow rendering rule details in smaller
font, so that it can fit in panel view
- Minor UI updates to gutter sizes and spacing to accommodate long text
- Extracted `createdBy` and `updatedBy` to
`~/security_solution/public/detections/components/rules/rule_info` to be
shared between rule details page and flyout

**How to test**
- add `xpack.securitySolution.enableExperimental:
['securityFlyoutEnabled']` to the `kibana.dev.json` file
- go to the Alerts page, and click on the expand detail button on any
row of the table
- click on Overview, About, view Rule Summary, the rule preview panel
should pop up
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
parent
37a53b69cf
commit
c28cb61fd4
24 changed files with 572 additions and 125 deletions
|
@ -17,13 +17,12 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { has } from 'lodash';
|
||||
import {
|
||||
PREVIEW_SECTION,
|
||||
PREVIEW_SECTION_BACK_BUTTON,
|
||||
PREVIEW_SECTION_CLOSE_BUTTON,
|
||||
PREVIEW_SECTION_HEADER,
|
||||
PREVIEW_SECTION,
|
||||
} from './test_ids';
|
||||
import { useExpandableFlyoutContext } from '../..';
|
||||
import { BACK_BUTTON, CLOSE_BUTTON } from './translations';
|
||||
|
@ -124,7 +123,6 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
css={css`
|
||||
position: absolute;
|
||||
|
@ -132,20 +130,14 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
|
|||
bottom: 0;
|
||||
right: 0;
|
||||
left: ${left};
|
||||
background-color: ${euiTheme.colors.shadow};
|
||||
opacity: 0.5;
|
||||
z-index: 1000;
|
||||
`}
|
||||
/>
|
||||
>
|
||||
<EuiSplitPanel.Outer
|
||||
css={css`
|
||||
margin: ${euiTheme.size.xs};
|
||||
height: 99%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: ${left};
|
||||
z-index: 1000;
|
||||
box-shadow: 0px 0px 5px 5px ${euiTheme.colors.darkShade};
|
||||
`}
|
||||
className="eui-yScroll"
|
||||
data-test-subj={PREVIEW_SECTION}
|
||||
|
@ -162,7 +154,7 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
|
|||
</EuiSplitPanel.Inner>
|
||||
<EuiSplitPanel.Inner paddingSize="none">{component}</EuiSplitPanel.Inner>
|
||||
</EuiSplitPanel.Outer>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_f
|
|||
import {
|
||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION,
|
||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER,
|
||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE,
|
||||
DOCUMENT_DETAILS_FLYOUT_CREATED_BY,
|
||||
DOCUMENT_DETAILS_FLYOUT_UPDATED_BY,
|
||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY,
|
||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER,
|
||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT,
|
||||
|
@ -52,10 +55,14 @@ describe(
|
|||
cy.log('rule preview panel');
|
||||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView();
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).should('be.visible');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER).should('be.visible');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).should('be.visible');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible');
|
||||
|
||||
cy.log('title');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).scrollIntoView();
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).should('be.visible');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_CREATED_BY).should('be.visible');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_UPDATED_BY).should('be.visible');
|
||||
|
||||
cy.log('about');
|
||||
|
||||
|
@ -84,6 +91,10 @@ describe(
|
|||
.and('contain.text', 'Schedule');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible');
|
||||
toggleRulePreviewScheduleSection();
|
||||
|
||||
cy.log('footer');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).scrollIntoView();
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
RULE_PREVIEW_TITLE_TEST_ID,
|
||||
RULE_PREVIEW_RULE_CREATED_BY_TEST_ID,
|
||||
RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID,
|
||||
RULE_PREVIEW_BODY_TEST_ID,
|
||||
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
|
||||
RULE_PREVIEW_ABOUT_CONTENT_TEST_ID,
|
||||
|
@ -23,6 +26,18 @@ export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION =
|
|||
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER =
|
||||
getDataTestSubjectSelector('previewSectionHeader');
|
||||
|
||||
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE = getDataTestSubjectSelector(
|
||||
RULE_PREVIEW_TITLE_TEST_ID
|
||||
);
|
||||
|
||||
export const DOCUMENT_DETAILS_FLYOUT_CREATED_BY = getDataTestSubjectSelector(
|
||||
RULE_PREVIEW_RULE_CREATED_BY_TEST_ID
|
||||
);
|
||||
|
||||
export const DOCUMENT_DETAILS_FLYOUT_UPDATED_BY = getDataTestSubjectSelector(
|
||||
RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID
|
||||
);
|
||||
|
||||
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY =
|
||||
getDataTestSubjectSelector(RULE_PREVIEW_BODY_TEST_ID);
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ export const clickInvestigationGuideButton = () => {
|
|||
* Click `Rule summary` button to open rule preview panel
|
||||
*/
|
||||
export const clickRuleSummaryButton = () => {
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE).scrollIntoView();
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE)
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
|
|
|
@ -22,7 +22,6 @@ import type { Filter } from '@kbn/es-query';
|
|||
import { i18n as i18nTranslate } from '@kbn/i18n';
|
||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { noop, omit } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
@ -53,7 +52,6 @@ import {
|
|||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import type { UpdateDateRange } from '../../../../common/components/charts/common';
|
||||
import { FiltersGlobal } from '../../../../common/components/filters_global';
|
||||
import { FormattedDate } from '../../../../common/components/formatted_date';
|
||||
import {
|
||||
getDetectionEngineUrl,
|
||||
getRuleDetailsTabUrl,
|
||||
|
@ -81,6 +79,7 @@ import {
|
|||
getStepsData,
|
||||
redirectToDetections,
|
||||
} from '../../../../detections/pages/detection_engine/rules/helpers';
|
||||
import { CreatedBy, UpdatedBy } from '../../../../detections/components/rules/rule_info';
|
||||
import { useGlobalTime } from '../../../../common/containers/use_global_time';
|
||||
import { inputsSelectors } from '../../../../common/store/inputs';
|
||||
import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions';
|
||||
|
@ -468,33 +467,9 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
() =>
|
||||
rule ? (
|
||||
[
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.ruleDetails.ruleCreationDescription"
|
||||
defaultMessage="Created by: {by} on {date}"
|
||||
values={{
|
||||
by: rule?.created_by ?? i18n.UNKNOWN,
|
||||
date: (
|
||||
<FormattedDate
|
||||
value={rule?.created_at ?? new Date().toISOString()}
|
||||
fieldName="createdAt"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>,
|
||||
<CreatedBy createdBy={rule?.created_by} createdAt={rule?.created_at} />,
|
||||
rule?.updated_by != null ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.ruleDetails.ruleUpdateDescription"
|
||||
defaultMessage="Updated by: {by} on {date}"
|
||||
values={{
|
||||
by: rule?.updated_by ?? i18n.UNKNOWN,
|
||||
date: (
|
||||
<FormattedDate
|
||||
value={rule?.updated_at ?? new Date().toISOString()}
|
||||
fieldName="updatedAt"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<UpdatedBy updatedBy={rule?.updated_by} updatedAt={rule?.updated_at} />
|
||||
) : (
|
||||
''
|
||||
),
|
||||
|
|
|
@ -236,6 +236,13 @@ const OverrideColumn = styled(EuiFlexItem)`
|
|||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const OverrideValueColumn = styled(EuiFlexItem)`
|
||||
width: 30px;
|
||||
max-width: 30px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems[] => [
|
||||
{
|
||||
title: i18nSeverity.DEFAULT_SEVERITY,
|
||||
|
@ -248,7 +255,7 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems
|
|||
return {
|
||||
title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '',
|
||||
description: (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<OverrideColumn>
|
||||
<EuiToolTip
|
||||
content={severityItem.field}
|
||||
|
@ -257,14 +264,14 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems
|
|||
<>{`${severityItem.field}:`}</>
|
||||
</EuiToolTip>
|
||||
</OverrideColumn>
|
||||
<OverrideColumn>
|
||||
<OverrideValueColumn>
|
||||
<EuiToolTip
|
||||
content={severityItem.value}
|
||||
data-test-subj={`severityOverrideValue${index}`}
|
||||
>
|
||||
{defaultToEmptyTag(severityItem.value)}
|
||||
</EuiToolTip>
|
||||
</OverrideColumn>
|
||||
</OverrideValueColumn>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={'sortRight'} />
|
||||
</EuiFlexItem>
|
||||
|
@ -293,7 +300,7 @@ export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListIt
|
|||
return {
|
||||
title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '',
|
||||
description: (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<OverrideColumn>
|
||||
<EuiToolTip
|
||||
content={riskScoreItem.field}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp';
|
||||
import React, { memo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import type { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import type { DataViewBase, Filter } from '@kbn/es-query';
|
||||
import { FilterStateStore } from '@kbn/es-query';
|
||||
|
@ -68,11 +68,19 @@ const DescriptionListContainer = styled(EuiDescriptionList)`
|
|||
}
|
||||
`;
|
||||
|
||||
const panelViewStyle = css`
|
||||
dt {
|
||||
font-size: 90% !important;
|
||||
}
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
interface StepRuleDescriptionProps<T> {
|
||||
columns?: 'multi' | 'single' | 'singleSplit';
|
||||
data: unknown;
|
||||
indexPatterns?: DataViewBase;
|
||||
schema: FormSchema<T>;
|
||||
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||
}
|
||||
|
||||
export const StepRuleDescriptionComponent = <T,>({
|
||||
|
@ -80,6 +88,7 @@ export const StepRuleDescriptionComponent = <T,>({
|
|||
columns = 'multi',
|
||||
indexPatterns,
|
||||
schema,
|
||||
isInPanelView,
|
||||
}: StepRuleDescriptionProps<T>) => {
|
||||
const kibana = useKibana();
|
||||
const license = useLicense();
|
||||
|
@ -126,6 +135,16 @@ export const StepRuleDescriptionComponent = <T,>({
|
|||
);
|
||||
}
|
||||
|
||||
if (isInPanelView) {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="listItemColumnStepRuleDescriptionPanel">
|
||||
<EuiDescriptionList listItems={listItems} className={panelViewStyle} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="listItemColumnStepRuleDescription">
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { CreatedBy, UpdatedBy } from '.';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
|
||||
describe('Rule related info', () => {
|
||||
describe('<CreatedBy />', () => {
|
||||
it('should render created correctly when by and date are passed', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<CreatedBy
|
||||
createdBy="test"
|
||||
createdAt="2023-01-01T22:01:00.000Z"
|
||||
data-test-subj="createdBy"
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('createdBy')).toHaveTextContent(
|
||||
'Created by: test on Jan 1, 2023 @ 22:01:00.000'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render created unknown when created by is not available', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<CreatedBy createdAt="2023-01-01T22:01:00.000Z" data-test-subj="createdBy" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('createdBy')).toHaveTextContent(
|
||||
'Created by: Unknown on Jan 1, 2023 @ 22:01:00.000'
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('<UpdatedBy />', () => {
|
||||
it('should render updated by correctly when by and date are passed', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<UpdatedBy
|
||||
updatedBy="test"
|
||||
updatedAt="2023-01-01T22:01:00.000Z"
|
||||
data-test-subj="updatedBy"
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('updatedBy')).toHaveTextContent(
|
||||
'Updated by: test on Jan 1, 2023 @ 22:01:00.000'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render updated by correctly when updated by is not available', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<UpdatedBy updatedAt="2023-01-01T22:01:00.000Z" data-test-subj="updatedBy" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('updatedBy')).toHaveTextContent(
|
||||
'Updated by: Unknown on Jan 1, 2023 @ 22:01:00.000'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { UNKNOWN_TEXT } from './translations';
|
||||
import { FormattedDate } from '../../../../common/components/formatted_date';
|
||||
|
||||
interface CreatedByProps {
|
||||
createdBy?: string;
|
||||
createdAt?: string;
|
||||
['data-test-subj']?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by and created at text that are shown on rule details and rule preview in expandable flyout
|
||||
*/
|
||||
export const CreatedBy: React.FC<CreatedByProps> = ({
|
||||
createdBy,
|
||||
createdAt,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.ruleDetails.ruleCreationDescription"
|
||||
defaultMessage="Created by: {by} on {date}"
|
||||
values={{
|
||||
by: createdBy ?? UNKNOWN_TEXT,
|
||||
date: (
|
||||
<FormattedDate value={createdAt ?? new Date().toISOString()} fieldName="createdAt" />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CreatedBy.displayName = 'CreatedBy';
|
||||
|
||||
interface UpdatedByProps {
|
||||
updatedBy?: string;
|
||||
updatedAt?: string;
|
||||
['data-test-subj']?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated by and updated at text that are shown on rule details and rule preview in expandable flyout
|
||||
*/
|
||||
export const UpdatedBy: React.FC<UpdatedByProps> = ({
|
||||
updatedBy,
|
||||
updatedAt,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.ruleDetails.ruleUpdateDescription"
|
||||
defaultMessage="Updated by: {by} on {date}"
|
||||
values={{
|
||||
by: updatedBy ?? UNKNOWN_TEXT,
|
||||
date: (
|
||||
<FormattedDate value={updatedAt ?? new Date().toISOString()} fieldName="updatedAt" />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
UpdatedBy.displayName = 'UpdatedBy';
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const UNKNOWN_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleInfo.UnknownText',
|
||||
{
|
||||
defaultMessage: 'Unknown',
|
||||
}
|
||||
);
|
|
@ -57,6 +57,7 @@ interface StepAboutRuleReadOnlyProps {
|
|||
addPadding: boolean;
|
||||
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
||||
defaultValues: AboutStepRule;
|
||||
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||
}
|
||||
|
||||
const ThreeQuartersContainer = styled.div`
|
||||
|
@ -367,10 +368,16 @@ const StepAboutRuleReadOnlyComponent: FC<StepAboutRuleReadOnlyProps> = ({
|
|||
addPadding,
|
||||
defaultValues: data,
|
||||
descriptionColumns,
|
||||
isInPanelView = false,
|
||||
}) => {
|
||||
return (
|
||||
<StepContentWrapper data-test-subj="aboutStep" addPadding={addPadding}>
|
||||
<StepRuleDescription columns={descriptionColumns} schema={defaultSchema} data={data} />
|
||||
<StepRuleDescription
|
||||
columns={descriptionColumns}
|
||||
schema={defaultSchema}
|
||||
data={data}
|
||||
isInPanelView={isInPanelView}
|
||||
/>
|
||||
</StepContentWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -116,6 +116,7 @@ interface StepDefineRuleReadOnlyProps {
|
|||
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
||||
defaultValues: DefineStepRule;
|
||||
indexPattern: DataViewBase;
|
||||
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||
}
|
||||
|
||||
export const MyLabelButton = styled(EuiButtonEmpty)`
|
||||
|
@ -908,6 +909,7 @@ const StepDefineRuleReadOnlyComponent: FC<StepDefineRuleReadOnlyProps> = ({
|
|||
defaultValues: data,
|
||||
descriptionColumns,
|
||||
indexPattern,
|
||||
isInPanelView = false,
|
||||
}) => {
|
||||
const dataForDescription: Partial<DefineStepRule> = getStepDataDataSource(data);
|
||||
|
||||
|
@ -918,6 +920,7 @@ const StepDefineRuleReadOnlyComponent: FC<StepDefineRuleReadOnlyProps> = ({
|
|||
schema={filterRuleFieldsForType(schema, data.ruleType)}
|
||||
data={filterRuleFieldsForType(dataForDescription, data.ruleType)}
|
||||
indexPatterns={indexPattern}
|
||||
isInPanelView={isInPanelView}
|
||||
/>
|
||||
</StepContentWrapper>
|
||||
);
|
||||
|
|
|
@ -27,6 +27,7 @@ interface StepScheduleRuleReadOnlyProps {
|
|||
addPadding: boolean;
|
||||
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
||||
defaultValues: ScheduleStepRule;
|
||||
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||
}
|
||||
|
||||
const StepScheduleRuleComponent: FC<StepScheduleRuleProps> = ({
|
||||
|
@ -69,10 +70,16 @@ const StepScheduleRuleReadOnlyComponent: FC<StepScheduleRuleReadOnlyProps> = ({
|
|||
addPadding,
|
||||
defaultValues: data,
|
||||
descriptionColumns,
|
||||
isInPanelView = false,
|
||||
}) => {
|
||||
return (
|
||||
<StepContentWrapper addPadding={addPadding}>
|
||||
<StepRuleDescription columns={descriptionColumns} schema={schema} data={data} />
|
||||
<StepRuleDescription
|
||||
columns={descriptionColumns}
|
||||
schema={schema}
|
||||
data={data}
|
||||
isInPanelView={isInPanelView}
|
||||
/>
|
||||
</StepContentWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,7 +12,16 @@ import { PreviewPanelContext } from '../context';
|
|||
import { mockContextValue } from '../mocks/mock_preview_panel_context';
|
||||
import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context';
|
||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
import { getStepsData } from '../../../detections/pages/detection_engine/rules/helpers';
|
||||
import {
|
||||
mockAboutStepRule,
|
||||
mockDefineStepRule,
|
||||
mockScheduleStepRule,
|
||||
} from '../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock';
|
||||
import {
|
||||
RULE_PREVIEW_BODY_TEST_ID,
|
||||
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
|
||||
|
@ -21,27 +30,57 @@ import {
|
|||
RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID,
|
||||
RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID,
|
||||
RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID,
|
||||
RULE_PREVIEW_ACTIONS_HEADER_TEST_ID,
|
||||
RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID,
|
||||
RULE_PREVIEW_LOADING_TEST_ID,
|
||||
} from './test_ids';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock;
|
||||
jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback');
|
||||
|
||||
const mockGetStepsData = getStepsData as jest.Mock;
|
||||
jest.mock('../../../detections/pages/detection_engine/rules/helpers');
|
||||
|
||||
const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } });
|
||||
|
||||
const contextValue = {
|
||||
...mockContextValue,
|
||||
ruleId: 'rule id',
|
||||
};
|
||||
|
||||
describe('<RulePreview />', () => {
|
||||
beforeEach(() => {
|
||||
// (useAppToasts as jest.Mock).mockReturnValue(useAppToastsValueMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render rule preview and its sub sections', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({
|
||||
rule: { name: 'rule name', description: 'rule description' },
|
||||
});
|
||||
mockGetStepsData.mockReturnValue({
|
||||
aboutRuleData: mockAboutStepRule(),
|
||||
defineRuleData: mockDefineStepRule(),
|
||||
scheduleRuleData: mockScheduleStepRule(),
|
||||
ruleActionsData: { actions: ['action'] },
|
||||
});
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<PreviewPanelContext.Provider value={contextValue}>
|
||||
<RulePreview />
|
||||
</PreviewPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</ThemeProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_ABOUT_CONTENT_TEST_ID)).toBeInTheDocument();
|
||||
|
@ -49,16 +88,63 @@ describe('<RulePreview />', () => {
|
|||
expect(getByTestId(RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render rule preview when rule is null', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({});
|
||||
it('should not render actions if action is not available', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({
|
||||
rule: { name: 'rule name', description: 'rule description' },
|
||||
});
|
||||
mockGetStepsData.mockReturnValue({
|
||||
aboutRuleData: mockAboutStepRule(),
|
||||
defineRuleData: mockDefineStepRule(),
|
||||
scheduleRuleData: mockScheduleStepRule(),
|
||||
});
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<PreviewPanelContext.Provider value={contextValue}>
|
||||
<RulePreview />
|
||||
</PreviewPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</ThemeProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render loading spinner when rule is loading', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({ loading: true, rule: null });
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<PreviewPanelContext.Provider value={contextValue}>
|
||||
<RulePreview />
|
||||
</PreviewPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</ThemeProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId(RULE_PREVIEW_LOADING_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render rule preview when rule is null', () => {
|
||||
mockUseRuleWithFallback.mockReturnValue({});
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<PreviewPanelContext.Provider value={contextValue}>
|
||||
<RulePreview />
|
||||
</PreviewPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</ThemeProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
|
@ -4,40 +4,35 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import {
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
EuiPanel,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import type { Rule } from '../../../detection_engine/rule_management/logic';
|
||||
import { usePreviewPanelContext } from '../context';
|
||||
import { ExpandableSection } from '../../right/components/expandable_section';
|
||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
import type { Rule } from '../../../detection_engine/rule_management/logic';
|
||||
import { getStepsData } from '../../../detections/pages/detection_engine/rules/helpers';
|
||||
import { RulePreviewTitle } from './rule_preview_title';
|
||||
import { StepAboutRuleReadOnly } from '../../../detections/components/rules/step_about_rule';
|
||||
import { StepDefineRuleReadOnly } from '../../../detections/components/rules/step_define_rule';
|
||||
import { StepScheduleRuleReadOnly } from '../../../detections/components/rules/step_schedule_rule';
|
||||
import { StepRuleActionsReadOnly } from '../../../detections/components/rules/step_rule_actions';
|
||||
import {
|
||||
RULE_PREVIEW_BODY_TEST_ID,
|
||||
RULE_PREVIEW_ABOUT_TEST_ID,
|
||||
RULE_PREVIEW_DEFINITION_TEST_ID,
|
||||
RULE_PREVIEW_SCHEDULE_TEST_ID,
|
||||
RULE_PREVIEW_ACTIONS_TEST_ID,
|
||||
RULE_PREVIEW_LOADING_TEST_ID,
|
||||
} from './test_ids';
|
||||
import {
|
||||
RULE_PREVIEW_ABOUT_TEXT,
|
||||
RULE_PREVIEW_DEFINITION_TEXT,
|
||||
RULE_PREVIEW_SCHEDULE_TEXT,
|
||||
} from './translations';
|
||||
import * as i18n from './translations';
|
||||
|
||||
/**
|
||||
* Rule summary on a preview panel on top of the right section of expandable flyout
|
||||
*/
|
||||
export const RulePreview: React.FC = memo(() => {
|
||||
const { ruleId } = usePreviewPanelContext();
|
||||
const { ruleId, indexPattern } = usePreviewPanelContext();
|
||||
const [rule, setRule] = useState<Rule | null>(null);
|
||||
|
||||
const { rule: maybeRule, loading } = useRuleWithFallback(ruleId ?? '');
|
||||
const { rule: maybeRule, loading: ruleLoading } = useRuleWithFallback(ruleId ?? '');
|
||||
|
||||
// persist rule until refresh is complete
|
||||
useEffect(() => {
|
||||
|
@ -46,42 +41,88 @@ export const RulePreview: React.FC = memo(() => {
|
|||
}
|
||||
}, [maybeRule]);
|
||||
|
||||
if (loading) {
|
||||
return <EuiLoadingSpinner />;
|
||||
}
|
||||
const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } =
|
||||
rule != null
|
||||
? getStepsData({ rule, detailsView: true })
|
||||
: {
|
||||
aboutRuleData: null,
|
||||
defineRuleData: null,
|
||||
scheduleRuleData: null,
|
||||
ruleActionsData: null,
|
||||
};
|
||||
|
||||
const hasNotificationActions = Boolean(ruleActionsData?.actions?.length);
|
||||
const hasResponseActions = Boolean(ruleActionsData?.responseActions?.length);
|
||||
const hasActions = ruleActionsData != null && (hasNotificationActions || hasResponseActions);
|
||||
|
||||
return rule ? (
|
||||
<EuiPanel hasShadow={false} data-test-subj={RULE_PREVIEW_BODY_TEST_ID}>
|
||||
<EuiTitle>
|
||||
<h6>{rule.name}</h6>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule />
|
||||
<EuiPanel hasShadow={false} data-test-subj={RULE_PREVIEW_BODY_TEST_ID} className="eui-yScroll">
|
||||
<RulePreviewTitle rule={rule} />
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<ExpandableSection
|
||||
title={RULE_PREVIEW_ABOUT_TEXT}
|
||||
title={i18n.RULE_PREVIEW_ABOUT_TEXT}
|
||||
expanded
|
||||
data-test-subj={RULE_PREVIEW_ABOUT_TEST_ID}
|
||||
>
|
||||
<EuiText size="s">{rule.description}</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
{'About'}
|
||||
{aboutRuleData && (
|
||||
<StepAboutRuleReadOnly
|
||||
addPadding={false}
|
||||
descriptionColumns="single"
|
||||
defaultValues={aboutRuleData}
|
||||
isInPanelView
|
||||
/>
|
||||
)}
|
||||
</ExpandableSection>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule margin="l" />
|
||||
{defineRuleData && (
|
||||
<>
|
||||
<ExpandableSection
|
||||
title={RULE_PREVIEW_DEFINITION_TEXT}
|
||||
title={i18n.RULE_PREVIEW_DEFINITION_TEXT}
|
||||
expanded={false}
|
||||
data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID}
|
||||
>
|
||||
{'Definition'}
|
||||
<StepDefineRuleReadOnly
|
||||
addPadding={false}
|
||||
descriptionColumns="single"
|
||||
defaultValues={defineRuleData}
|
||||
indexPattern={indexPattern}
|
||||
isInPanelView
|
||||
/>
|
||||
</ExpandableSection>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule margin="l" />
|
||||
</>
|
||||
)}
|
||||
{scheduleRuleData && (
|
||||
<>
|
||||
<ExpandableSection
|
||||
title={RULE_PREVIEW_SCHEDULE_TEXT}
|
||||
title={i18n.RULE_PREVIEW_SCHEDULE_TEXT}
|
||||
expanded={false}
|
||||
data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID}
|
||||
>
|
||||
{'Schedule'}
|
||||
<StepScheduleRuleReadOnly
|
||||
addPadding={false}
|
||||
descriptionColumns="single"
|
||||
defaultValues={scheduleRuleData}
|
||||
isInPanelView
|
||||
/>
|
||||
</ExpandableSection>
|
||||
<EuiHorizontalRule margin="l" />
|
||||
</>
|
||||
)}
|
||||
{hasActions && (
|
||||
<ExpandableSection
|
||||
title={i18n.RULE_PREVIEW_ACTIONS_TEXT}
|
||||
expanded={false}
|
||||
data-test-subj={RULE_PREVIEW_ACTIONS_TEST_ID}
|
||||
>
|
||||
<StepRuleActionsReadOnly addPadding={false} defaultValues={ruleActionsData} />
|
||||
</ExpandableSection>
|
||||
)}
|
||||
</EuiPanel>
|
||||
) : ruleLoading ? (
|
||||
<EuiLoadingSpinner size="l" data-test-subj={RULE_PREVIEW_LOADING_TEST_ID} />
|
||||
) : null;
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { RulePreviewTitle } from './rule_preview_title';
|
||||
import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context';
|
||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import type { Rule } from '../../../detection_engine/rule_management/logic';
|
||||
import {
|
||||
RULE_PREVIEW_TITLE_TEST_ID,
|
||||
RULE_PREVIEW_RULE_CREATED_BY_TEST_ID,
|
||||
RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID,
|
||||
} from './test_ids';
|
||||
|
||||
const defaultProps = {
|
||||
rule: { id: 'id' } as Rule,
|
||||
};
|
||||
|
||||
describe('<RulePreviewTitle />', () => {
|
||||
it('should render title and its components', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RulePreviewTitle {...defaultProps} />
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId(RULE_PREVIEW_TITLE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_RULE_CREATED_BY_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { EuiTitle, EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import type { Rule } from '../../../detection_engine/rule_management/logic';
|
||||
import { CreatedBy, UpdatedBy } from '../../../detections/components/rules/rule_info';
|
||||
import {
|
||||
RULE_PREVIEW_TITLE_TEST_ID,
|
||||
RULE_PREVIEW_RULE_CREATED_BY_TEST_ID,
|
||||
RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID,
|
||||
} from './test_ids';
|
||||
|
||||
interface RulePreviewTitleProps {
|
||||
/**
|
||||
* Rule object that represents relevant information about a rule
|
||||
*/
|
||||
rule: Rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title component that shows basic information of a rule. This is displayed above rule preview body in rule preview panel
|
||||
*/
|
||||
export const RulePreviewTitle: React.FC<RulePreviewTitleProps> = ({ rule }) => {
|
||||
return (
|
||||
<div data-test-subj={RULE_PREVIEW_TITLE_TEST_ID}>
|
||||
<EuiTitle>
|
||||
<h6>{rule.name}</h6>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup gutterSize="xs" direction="column">
|
||||
<EuiFlexItem data-test-subj={RULE_PREVIEW_RULE_CREATED_BY_TEST_ID}>
|
||||
<EuiText size="xs">
|
||||
<CreatedBy createdBy={rule?.created_by} createdAt={rule?.created_at} />
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID}>
|
||||
<EuiText size="xs">
|
||||
<UpdatedBy updatedBy={rule?.updated_by} updatedAt={rule?.updated_at} />
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RulePreviewTitle.displayName = 'RulePreviewTitle';
|
|
@ -9,6 +9,12 @@ import { CONTENT_TEST_ID, HEADER_TEST_ID } from '../../right/components/expandab
|
|||
|
||||
/* Rule preview */
|
||||
|
||||
export const RULE_PREVIEW_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewTitle';
|
||||
export const RULE_PREVIEW_RULE_CREATED_BY_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutRulePreviewCreatedByText';
|
||||
export const RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutRulePreviewUpdatedByText';
|
||||
|
||||
export const RULE_PREVIEW_BODY_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewBody';
|
||||
export const RULE_PREVIEW_ABOUT_TEST_ID = `securitySolutionDocumentDetailsFlyoutRulePreviewAboutSection`;
|
||||
export const RULE_PREVIEW_ABOUT_HEADER_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + HEADER_TEST_ID;
|
||||
|
@ -24,5 +30,12 @@ export const RULE_PREVIEW_SCHEDULE_TEST_ID =
|
|||
export const RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID = RULE_PREVIEW_SCHEDULE_TEST_ID + HEADER_TEST_ID;
|
||||
export const RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID =
|
||||
RULE_PREVIEW_SCHEDULE_TEST_ID + CONTENT_TEST_ID;
|
||||
export const RULE_PREVIEW_ACTIONS_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutRulePreviewActionsSection';
|
||||
export const RULE_PREVIEW_ACTIONS_HEADER_TEST_ID = RULE_PREVIEW_ACTIONS_TEST_ID + HEADER_TEST_ID;
|
||||
export const RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID = RULE_PREVIEW_ACTIONS_TEST_ID + CONTENT_TEST_ID;
|
||||
export const RULE_PREVIEW_LOADING_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutRulePreviewLoadingSpinner';
|
||||
export const RULE_PREVIEW_HEADER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewHeader';
|
||||
export const RULE_PREVIEW_FOOTER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewFooter';
|
||||
export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID = 'goToRuleDetails';
|
||||
|
|
|
@ -26,3 +26,13 @@ export const RULE_PREVIEW_SCHEDULE_TEXT = i18n.translate(
|
|||
'xpack.securitySolution.flyout.documentDetails.rulePreviewScheduleSectionText',
|
||||
{ defaultMessage: 'Schedule' }
|
||||
);
|
||||
|
||||
export const RULE_PREVIEW_ACTIONS_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.rulePreviewActionsSectionText',
|
||||
{ defaultMessage: 'Actions' }
|
||||
);
|
||||
|
||||
export const ENABLE_RULE_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.rulePreviewEnableRuleText',
|
||||
{ defaultMessage: 'Enable' }
|
||||
);
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
*/
|
||||
|
||||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import type { DataViewBase } from '@kbn/es-query';
|
||||
import type { PreviewPanelProps } from '.';
|
||||
import { useRouteSpy } from '../../common/utils/route/use_route_spy';
|
||||
import { SecurityPageName } from '../../../common/constants';
|
||||
import { SourcererScopeName } from '../../common/store/sourcerer/model';
|
||||
import { useSourcererDataView } from '../../common/containers/sourcerer';
|
||||
|
||||
export interface PreviewPanelContext {
|
||||
/**
|
||||
|
@ -25,6 +30,10 @@ export interface PreviewPanelContext {
|
|||
* Rule id if preview is rule details
|
||||
*/
|
||||
ruleId: string;
|
||||
/**
|
||||
* Index pattern for rule details
|
||||
*/
|
||||
indexPattern: DataViewBase;
|
||||
}
|
||||
|
||||
export const PreviewPanelContext = createContext<PreviewPanelContext | undefined>(undefined);
|
||||
|
@ -43,6 +52,12 @@ export const PreviewPanelProvider = ({
|
|||
ruleId,
|
||||
children,
|
||||
}: PreviewPanelProviderProps) => {
|
||||
const [{ pageName }] = useRouteSpy();
|
||||
const sourcererScope =
|
||||
pageName === SecurityPageName.detections
|
||||
? SourcererScopeName.detections
|
||||
: SourcererScopeName.default;
|
||||
const sourcererDataView = useSourcererDataView(sourcererScope);
|
||||
const contextValue = useMemo(
|
||||
() =>
|
||||
id && indexName && scopeId
|
||||
|
@ -51,9 +66,10 @@ export const PreviewPanelProvider = ({
|
|||
indexName,
|
||||
scopeId,
|
||||
ruleId: ruleId ?? '',
|
||||
indexPattern: sourcererDataView.indexPattern,
|
||||
}
|
||||
: undefined,
|
||||
[id, indexName, scopeId, ruleId]
|
||||
[id, indexName, scopeId, ruleId, sourcererDataView.indexPattern]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { panels } from './panels';
|
||||
|
@ -21,7 +20,6 @@ export interface PreviewPanelProps extends FlyoutPanelProps {
|
|||
id: string;
|
||||
indexName: string;
|
||||
scopeId: string;
|
||||
banner?: string;
|
||||
ruleId?: string;
|
||||
};
|
||||
}
|
||||
|
@ -38,14 +36,13 @@ export const PreviewPanel: React.FC<Partial<PreviewPanelProps>> = memo(({ path }
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" direction="column" className="eui-fullHeight">
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
margin-top: -15px;
|
||||
`}
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
{previewPanel.content}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ marginTop: '-15px' }}>{previewPanel.content}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{previewPanel.footer}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -15,4 +15,5 @@ export const mockContextValue: PreviewPanelContext = {
|
|||
indexName: 'index',
|
||||
scopeId: 'scopeId',
|
||||
ruleId: '',
|
||||
indexPattern: { fields: [], title: 'test index' },
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ export type PreviewPanelType = Array<{
|
|||
/**
|
||||
* Footer section in the panel
|
||||
*/
|
||||
footer: React.ReactElement;
|
||||
footer?: React.ReactElement;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,7 +61,7 @@ export const Description: FC = () => {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
iconType="arrowRight"
|
||||
iconType="expand"
|
||||
onClick={openRulePreview}
|
||||
iconSide="right"
|
||||
data-test-subj={RULE_SUMMARY_BUTTON_TEST_ID}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue