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';
|
} from '@elastic/eui';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
import { has } from 'lodash';
|
import { has } from 'lodash';
|
||||||
import {
|
import {
|
||||||
PREVIEW_SECTION,
|
|
||||||
PREVIEW_SECTION_BACK_BUTTON,
|
PREVIEW_SECTION_BACK_BUTTON,
|
||||||
PREVIEW_SECTION_CLOSE_BUTTON,
|
PREVIEW_SECTION_CLOSE_BUTTON,
|
||||||
PREVIEW_SECTION_HEADER,
|
PREVIEW_SECTION_HEADER,
|
||||||
|
PREVIEW_SECTION,
|
||||||
} from './test_ids';
|
} from './test_ids';
|
||||||
import { useExpandableFlyoutContext } from '../..';
|
import { useExpandableFlyoutContext } from '../..';
|
||||||
import { BACK_BUTTON, CLOSE_BUTTON } from './translations';
|
import { BACK_BUTTON, CLOSE_BUTTON } from './translations';
|
||||||
|
@ -124,28 +123,21 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
<div
|
css={css`
|
||||||
css={css`
|
position: absolute;
|
||||||
position: absolute;
|
top: 0;
|
||||||
top: 0;
|
bottom: 0;
|
||||||
bottom: 0;
|
right: 0;
|
||||||
right: 0;
|
left: ${left};
|
||||||
left: ${left};
|
z-index: 1000;
|
||||||
background-color: ${euiTheme.colors.shadow};
|
`}
|
||||||
opacity: 0.5;
|
>
|
||||||
`}
|
|
||||||
/>
|
|
||||||
<EuiSplitPanel.Outer
|
<EuiSplitPanel.Outer
|
||||||
css={css`
|
css={css`
|
||||||
margin: ${euiTheme.size.xs};
|
margin: ${euiTheme.size.xs};
|
||||||
height: 99%;
|
height: 99%;
|
||||||
position: absolute;
|
box-shadow: 0px 0px 5px 5px ${euiTheme.colors.darkShade};
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
left: ${left};
|
|
||||||
z-index: 1000;
|
|
||||||
`}
|
`}
|
||||||
className="eui-yScroll"
|
className="eui-yScroll"
|
||||||
data-test-subj={PREVIEW_SECTION}
|
data-test-subj={PREVIEW_SECTION}
|
||||||
|
@ -162,7 +154,7 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
|
||||||
</EuiSplitPanel.Inner>
|
</EuiSplitPanel.Inner>
|
||||||
<EuiSplitPanel.Inner paddingSize="none">{component}</EuiSplitPanel.Inner>
|
<EuiSplitPanel.Inner paddingSize="none">{component}</EuiSplitPanel.Inner>
|
||||||
</EuiSplitPanel.Outer>
|
</EuiSplitPanel.Outer>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_f
|
||||||
import {
|
import {
|
||||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION,
|
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION,
|
||||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER,
|
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_BODY,
|
||||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER,
|
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER,
|
||||||
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT,
|
DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT,
|
||||||
|
@ -52,10 +55,14 @@ describe(
|
||||||
cy.log('rule preview panel');
|
cy.log('rule preview panel');
|
||||||
|
|
||||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView();
|
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_HEADER).should('be.visible');
|
||||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).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');
|
cy.log('about');
|
||||||
|
|
||||||
|
@ -84,6 +91,10 @@ describe(
|
||||||
.and('contain.text', 'Schedule');
|
.and('contain.text', 'Schedule');
|
||||||
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible');
|
cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible');
|
||||||
toggleRulePreviewScheduleSection();
|
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 {
|
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_BODY_TEST_ID,
|
||||||
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
|
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
|
||||||
RULE_PREVIEW_ABOUT_CONTENT_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 =
|
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER =
|
||||||
getDataTestSubjectSelector('previewSectionHeader');
|
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 =
|
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY =
|
||||||
getDataTestSubjectSelector(RULE_PREVIEW_BODY_TEST_ID);
|
getDataTestSubjectSelector(RULE_PREVIEW_BODY_TEST_ID);
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ export const clickInvestigationGuideButton = () => {
|
||||||
* Click `Rule summary` button to open rule preview panel
|
* Click `Rule summary` button to open rule preview panel
|
||||||
*/
|
*/
|
||||||
export const clickRuleSummaryButton = () => {
|
export const clickRuleSummaryButton = () => {
|
||||||
|
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE).scrollIntoView();
|
||||||
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE)
|
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.within(() => {
|
.within(() => {
|
||||||
|
|
|
@ -22,7 +22,6 @@ import type { Filter } from '@kbn/es-query';
|
||||||
import { i18n as i18nTranslate } from '@kbn/i18n';
|
import { i18n as i18nTranslate } from '@kbn/i18n';
|
||||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||||
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import { noop, omit } from 'lodash/fp';
|
import { noop, omit } from 'lodash/fp';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
@ -53,7 +52,6 @@ import {
|
||||||
import { useKibana } from '../../../../common/lib/kibana';
|
import { useKibana } from '../../../../common/lib/kibana';
|
||||||
import type { UpdateDateRange } from '../../../../common/components/charts/common';
|
import type { UpdateDateRange } from '../../../../common/components/charts/common';
|
||||||
import { FiltersGlobal } from '../../../../common/components/filters_global';
|
import { FiltersGlobal } from '../../../../common/components/filters_global';
|
||||||
import { FormattedDate } from '../../../../common/components/formatted_date';
|
|
||||||
import {
|
import {
|
||||||
getDetectionEngineUrl,
|
getDetectionEngineUrl,
|
||||||
getRuleDetailsTabUrl,
|
getRuleDetailsTabUrl,
|
||||||
|
@ -81,6 +79,7 @@ import {
|
||||||
getStepsData,
|
getStepsData,
|
||||||
redirectToDetections,
|
redirectToDetections,
|
||||||
} from '../../../../detections/pages/detection_engine/rules/helpers';
|
} 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 { useGlobalTime } from '../../../../common/containers/use_global_time';
|
||||||
import { inputsSelectors } from '../../../../common/store/inputs';
|
import { inputsSelectors } from '../../../../common/store/inputs';
|
||||||
import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions';
|
import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions';
|
||||||
|
@ -468,33 +467,9 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
||||||
() =>
|
() =>
|
||||||
rule ? (
|
rule ? (
|
||||||
[
|
[
|
||||||
<FormattedMessage
|
<CreatedBy createdBy={rule?.created_by} createdAt={rule?.created_at} />,
|
||||||
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"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
rule?.updated_by != null ? (
|
rule?.updated_by != null ? (
|
||||||
<FormattedMessage
|
<UpdatedBy updatedBy={rule?.updated_by} updatedAt={rule?.updated_at} />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
),
|
),
|
||||||
|
|
|
@ -236,6 +236,13 @@ const OverrideColumn = styled(EuiFlexItem)`
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const OverrideValueColumn = styled(EuiFlexItem)`
|
||||||
|
width: 30px;
|
||||||
|
max-width: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`;
|
||||||
|
|
||||||
export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems[] => [
|
export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems[] => [
|
||||||
{
|
{
|
||||||
title: i18nSeverity.DEFAULT_SEVERITY,
|
title: i18nSeverity.DEFAULT_SEVERITY,
|
||||||
|
@ -248,7 +255,7 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems
|
||||||
return {
|
return {
|
||||||
title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '',
|
title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '',
|
||||||
description: (
|
description: (
|
||||||
<EuiFlexGroup alignItems="center">
|
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||||
<OverrideColumn>
|
<OverrideColumn>
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
content={severityItem.field}
|
content={severityItem.field}
|
||||||
|
@ -257,14 +264,14 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems
|
||||||
<>{`${severityItem.field}:`}</>
|
<>{`${severityItem.field}:`}</>
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
</OverrideColumn>
|
</OverrideColumn>
|
||||||
<OverrideColumn>
|
<OverrideValueColumn>
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
content={severityItem.value}
|
content={severityItem.value}
|
||||||
data-test-subj={`severityOverrideValue${index}`}
|
data-test-subj={`severityOverrideValue${index}`}
|
||||||
>
|
>
|
||||||
{defaultToEmptyTag(severityItem.value)}
|
{defaultToEmptyTag(severityItem.value)}
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
</OverrideColumn>
|
</OverrideValueColumn>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiIcon type={'sortRight'} />
|
<EuiIcon type={'sortRight'} />
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
@ -293,7 +300,7 @@ export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListIt
|
||||||
return {
|
return {
|
||||||
title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '',
|
title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '',
|
||||||
description: (
|
description: (
|
||||||
<EuiFlexGroup alignItems="center">
|
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||||
<OverrideColumn>
|
<OverrideColumn>
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
content={riskScoreItem.field}
|
content={riskScoreItem.field}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp';
|
import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp';
|
||||||
import React, { memo, useState } from 'react';
|
import React, { memo, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
import type { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-alerting-types';
|
import type { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||||
import type { DataViewBase, Filter } from '@kbn/es-query';
|
import type { DataViewBase, Filter } from '@kbn/es-query';
|
||||||
import { FilterStateStore } 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> {
|
interface StepRuleDescriptionProps<T> {
|
||||||
columns?: 'multi' | 'single' | 'singleSplit';
|
columns?: 'multi' | 'single' | 'singleSplit';
|
||||||
data: unknown;
|
data: unknown;
|
||||||
indexPatterns?: DataViewBase;
|
indexPatterns?: DataViewBase;
|
||||||
schema: FormSchema<T>;
|
schema: FormSchema<T>;
|
||||||
|
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StepRuleDescriptionComponent = <T,>({
|
export const StepRuleDescriptionComponent = <T,>({
|
||||||
|
@ -80,6 +88,7 @@ export const StepRuleDescriptionComponent = <T,>({
|
||||||
columns = 'multi',
|
columns = 'multi',
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
schema,
|
schema,
|
||||||
|
isInPanelView,
|
||||||
}: StepRuleDescriptionProps<T>) => {
|
}: StepRuleDescriptionProps<T>) => {
|
||||||
const kibana = useKibana();
|
const kibana = useKibana();
|
||||||
const license = useLicense();
|
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 (
|
return (
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem data-test-subj="listItemColumnStepRuleDescription">
|
<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;
|
addPadding: boolean;
|
||||||
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
||||||
defaultValues: AboutStepRule;
|
defaultValues: AboutStepRule;
|
||||||
|
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThreeQuartersContainer = styled.div`
|
const ThreeQuartersContainer = styled.div`
|
||||||
|
@ -367,10 +368,16 @@ const StepAboutRuleReadOnlyComponent: FC<StepAboutRuleReadOnlyProps> = ({
|
||||||
addPadding,
|
addPadding,
|
||||||
defaultValues: data,
|
defaultValues: data,
|
||||||
descriptionColumns,
|
descriptionColumns,
|
||||||
|
isInPanelView = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<StepContentWrapper data-test-subj="aboutStep" addPadding={addPadding}>
|
<StepContentWrapper data-test-subj="aboutStep" addPadding={addPadding}>
|
||||||
<StepRuleDescription columns={descriptionColumns} schema={defaultSchema} data={data} />
|
<StepRuleDescription
|
||||||
|
columns={descriptionColumns}
|
||||||
|
schema={defaultSchema}
|
||||||
|
data={data}
|
||||||
|
isInPanelView={isInPanelView}
|
||||||
|
/>
|
||||||
</StepContentWrapper>
|
</StepContentWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -116,6 +116,7 @@ interface StepDefineRuleReadOnlyProps {
|
||||||
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
||||||
defaultValues: DefineStepRule;
|
defaultValues: DefineStepRule;
|
||||||
indexPattern: DataViewBase;
|
indexPattern: DataViewBase;
|
||||||
|
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MyLabelButton = styled(EuiButtonEmpty)`
|
export const MyLabelButton = styled(EuiButtonEmpty)`
|
||||||
|
@ -908,6 +909,7 @@ const StepDefineRuleReadOnlyComponent: FC<StepDefineRuleReadOnlyProps> = ({
|
||||||
defaultValues: data,
|
defaultValues: data,
|
||||||
descriptionColumns,
|
descriptionColumns,
|
||||||
indexPattern,
|
indexPattern,
|
||||||
|
isInPanelView = false,
|
||||||
}) => {
|
}) => {
|
||||||
const dataForDescription: Partial<DefineStepRule> = getStepDataDataSource(data);
|
const dataForDescription: Partial<DefineStepRule> = getStepDataDataSource(data);
|
||||||
|
|
||||||
|
@ -918,6 +920,7 @@ const StepDefineRuleReadOnlyComponent: FC<StepDefineRuleReadOnlyProps> = ({
|
||||||
schema={filterRuleFieldsForType(schema, data.ruleType)}
|
schema={filterRuleFieldsForType(schema, data.ruleType)}
|
||||||
data={filterRuleFieldsForType(dataForDescription, data.ruleType)}
|
data={filterRuleFieldsForType(dataForDescription, data.ruleType)}
|
||||||
indexPatterns={indexPattern}
|
indexPatterns={indexPattern}
|
||||||
|
isInPanelView={isInPanelView}
|
||||||
/>
|
/>
|
||||||
</StepContentWrapper>
|
</StepContentWrapper>
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,6 +27,7 @@ interface StepScheduleRuleReadOnlyProps {
|
||||||
addPadding: boolean;
|
addPadding: boolean;
|
||||||
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
descriptionColumns: 'multi' | 'single' | 'singleSplit';
|
||||||
defaultValues: ScheduleStepRule;
|
defaultValues: ScheduleStepRule;
|
||||||
|
isInPanelView?: boolean; // Option to show description list in smaller font
|
||||||
}
|
}
|
||||||
|
|
||||||
const StepScheduleRuleComponent: FC<StepScheduleRuleProps> = ({
|
const StepScheduleRuleComponent: FC<StepScheduleRuleProps> = ({
|
||||||
|
@ -69,10 +70,16 @@ const StepScheduleRuleReadOnlyComponent: FC<StepScheduleRuleReadOnlyProps> = ({
|
||||||
addPadding,
|
addPadding,
|
||||||
defaultValues: data,
|
defaultValues: data,
|
||||||
descriptionColumns,
|
descriptionColumns,
|
||||||
|
isInPanelView = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<StepContentWrapper addPadding={addPadding}>
|
<StepContentWrapper addPadding={addPadding}>
|
||||||
<StepRuleDescription columns={descriptionColumns} schema={schema} data={data} />
|
<StepRuleDescription
|
||||||
|
columns={descriptionColumns}
|
||||||
|
schema={schema}
|
||||||
|
data={data}
|
||||||
|
isInPanelView={isInPanelView}
|
||||||
|
/>
|
||||||
</StepContentWrapper>
|
</StepContentWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,16 @@ import { PreviewPanelContext } from '../context';
|
||||||
import { mockContextValue } from '../mocks/mock_preview_panel_context';
|
import { mockContextValue } from '../mocks/mock_preview_panel_context';
|
||||||
import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context';
|
import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context';
|
||||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/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 { 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 {
|
import {
|
||||||
RULE_PREVIEW_BODY_TEST_ID,
|
RULE_PREVIEW_BODY_TEST_ID,
|
||||||
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
|
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
|
||||||
|
@ -21,27 +30,57 @@ import {
|
||||||
RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID,
|
RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID,
|
||||||
RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID,
|
RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID,
|
||||||
RULE_PREVIEW_SCHEDULE_CONTENT_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';
|
} from './test_ids';
|
||||||
|
|
||||||
|
jest.mock('../../../common/lib/kibana');
|
||||||
|
|
||||||
const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock;
|
const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock;
|
||||||
jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback');
|
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 = {
|
const contextValue = {
|
||||||
...mockContextValue,
|
...mockContextValue,
|
||||||
ruleId: 'rule id',
|
ruleId: 'rule id',
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('<RulePreview />', () => {
|
describe('<RulePreview />', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// (useAppToasts as jest.Mock).mockReturnValue(useAppToastsValueMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('should render rule preview and its sub sections', () => {
|
it('should render rule preview and its sub sections', () => {
|
||||||
mockUseRuleWithFallback.mockReturnValue({
|
mockUseRuleWithFallback.mockReturnValue({
|
||||||
rule: { name: 'rule name', description: 'rule description' },
|
rule: { name: 'rule name', description: 'rule description' },
|
||||||
});
|
});
|
||||||
|
mockGetStepsData.mockReturnValue({
|
||||||
|
aboutRuleData: mockAboutStepRule(),
|
||||||
|
defineRuleData: mockDefineStepRule(),
|
||||||
|
scheduleRuleData: mockScheduleStepRule(),
|
||||||
|
ruleActionsData: { actions: ['action'] },
|
||||||
|
});
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
<TestProviders>
|
||||||
<PreviewPanelContext.Provider value={contextValue}>
|
<ThemeProvider theme={mockTheme}>
|
||||||
<RulePreview />
|
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||||
</PreviewPanelContext.Provider>
|
<PreviewPanelContext.Provider value={contextValue}>
|
||||||
</ExpandableFlyoutContext.Provider>
|
<RulePreview />
|
||||||
|
</PreviewPanelContext.Provider>
|
||||||
|
</ExpandableFlyoutContext.Provider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument();
|
expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument();
|
||||||
expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument();
|
expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument();
|
||||||
expect(getByTestId(RULE_PREVIEW_ABOUT_CONTENT_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_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument();
|
||||||
expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_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_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 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', () => {
|
it('should not render rule preview when rule is null', () => {
|
||||||
mockUseRuleWithFallback.mockReturnValue({});
|
mockUseRuleWithFallback.mockReturnValue({});
|
||||||
const { queryByTestId } = render(
|
const { queryByTestId } = render(
|
||||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
<TestProviders>
|
||||||
<PreviewPanelContext.Provider value={contextValue}>
|
<ThemeProvider theme={mockTheme}>
|
||||||
<RulePreview />
|
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||||
</PreviewPanelContext.Provider>
|
<PreviewPanelContext.Provider value={contextValue}>
|
||||||
</ExpandableFlyoutContext.Provider>
|
<RulePreview />
|
||||||
|
</PreviewPanelContext.Provider>
|
||||||
|
</ExpandableFlyoutContext.Provider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</TestProviders>
|
||||||
);
|
);
|
||||||
expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument();
|
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; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useState, useEffect } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import {
|
import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel, EuiLoadingSpinner } from '@elastic/eui';
|
||||||
EuiTitle,
|
import type { Rule } from '../../../detection_engine/rule_management/logic';
|
||||||
EuiText,
|
|
||||||
EuiHorizontalRule,
|
|
||||||
EuiSpacer,
|
|
||||||
EuiPanel,
|
|
||||||
EuiLoadingSpinner,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { usePreviewPanelContext } from '../context';
|
import { usePreviewPanelContext } from '../context';
|
||||||
import { ExpandableSection } from '../../right/components/expandable_section';
|
import { ExpandableSection } from '../../right/components/expandable_section';
|
||||||
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
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 {
|
import {
|
||||||
RULE_PREVIEW_BODY_TEST_ID,
|
RULE_PREVIEW_BODY_TEST_ID,
|
||||||
RULE_PREVIEW_ABOUT_TEST_ID,
|
RULE_PREVIEW_ABOUT_TEST_ID,
|
||||||
RULE_PREVIEW_DEFINITION_TEST_ID,
|
RULE_PREVIEW_DEFINITION_TEST_ID,
|
||||||
RULE_PREVIEW_SCHEDULE_TEST_ID,
|
RULE_PREVIEW_SCHEDULE_TEST_ID,
|
||||||
|
RULE_PREVIEW_ACTIONS_TEST_ID,
|
||||||
|
RULE_PREVIEW_LOADING_TEST_ID,
|
||||||
} from './test_ids';
|
} from './test_ids';
|
||||||
import {
|
import * as i18n from './translations';
|
||||||
RULE_PREVIEW_ABOUT_TEXT,
|
|
||||||
RULE_PREVIEW_DEFINITION_TEXT,
|
|
||||||
RULE_PREVIEW_SCHEDULE_TEXT,
|
|
||||||
} from './translations';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rule summary on a preview panel on top of the right section of expandable flyout
|
* Rule summary on a preview panel on top of the right section of expandable flyout
|
||||||
*/
|
*/
|
||||||
export const RulePreview: React.FC = memo(() => {
|
export const RulePreview: React.FC = memo(() => {
|
||||||
const { ruleId } = usePreviewPanelContext();
|
const { ruleId, indexPattern } = usePreviewPanelContext();
|
||||||
const [rule, setRule] = useState<Rule | null>(null);
|
const [rule, setRule] = useState<Rule | null>(null);
|
||||||
|
const { rule: maybeRule, loading: ruleLoading } = useRuleWithFallback(ruleId ?? '');
|
||||||
const { rule: maybeRule, loading } = useRuleWithFallback(ruleId ?? '');
|
|
||||||
|
|
||||||
// persist rule until refresh is complete
|
// persist rule until refresh is complete
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -46,42 +41,88 @@ export const RulePreview: React.FC = memo(() => {
|
||||||
}
|
}
|
||||||
}, [maybeRule]);
|
}, [maybeRule]);
|
||||||
|
|
||||||
if (loading) {
|
const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } =
|
||||||
return <EuiLoadingSpinner />;
|
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 ? (
|
return rule ? (
|
||||||
<EuiPanel hasShadow={false} data-test-subj={RULE_PREVIEW_BODY_TEST_ID}>
|
<EuiPanel hasShadow={false} data-test-subj={RULE_PREVIEW_BODY_TEST_ID} className="eui-yScroll">
|
||||||
<EuiTitle>
|
<RulePreviewTitle rule={rule} />
|
||||||
<h6>{rule.name}</h6>
|
<EuiHorizontalRule margin="s" />
|
||||||
</EuiTitle>
|
|
||||||
<EuiHorizontalRule />
|
|
||||||
<ExpandableSection
|
<ExpandableSection
|
||||||
title={RULE_PREVIEW_ABOUT_TEXT}
|
title={i18n.RULE_PREVIEW_ABOUT_TEXT}
|
||||||
expanded
|
expanded
|
||||||
data-test-subj={RULE_PREVIEW_ABOUT_TEST_ID}
|
data-test-subj={RULE_PREVIEW_ABOUT_TEST_ID}
|
||||||
>
|
>
|
||||||
<EuiText size="s">{rule.description}</EuiText>
|
<EuiText size="s">{rule.description}</EuiText>
|
||||||
<EuiSpacer size="s" />
|
<EuiSpacer size="s" />
|
||||||
{'About'}
|
{aboutRuleData && (
|
||||||
</ExpandableSection>
|
<StepAboutRuleReadOnly
|
||||||
<EuiSpacer size="m" />
|
addPadding={false}
|
||||||
<ExpandableSection
|
descriptionColumns="single"
|
||||||
title={RULE_PREVIEW_DEFINITION_TEXT}
|
defaultValues={aboutRuleData}
|
||||||
expanded={false}
|
isInPanelView
|
||||||
data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID}
|
/>
|
||||||
>
|
)}
|
||||||
{'Definition'}
|
|
||||||
</ExpandableSection>
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
<ExpandableSection
|
|
||||||
title={RULE_PREVIEW_SCHEDULE_TEXT}
|
|
||||||
expanded={false}
|
|
||||||
data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID}
|
|
||||||
>
|
|
||||||
{'Schedule'}
|
|
||||||
</ExpandableSection>
|
</ExpandableSection>
|
||||||
|
<EuiHorizontalRule margin="l" />
|
||||||
|
{defineRuleData && (
|
||||||
|
<>
|
||||||
|
<ExpandableSection
|
||||||
|
title={i18n.RULE_PREVIEW_DEFINITION_TEXT}
|
||||||
|
expanded={false}
|
||||||
|
data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID}
|
||||||
|
>
|
||||||
|
<StepDefineRuleReadOnly
|
||||||
|
addPadding={false}
|
||||||
|
descriptionColumns="single"
|
||||||
|
defaultValues={defineRuleData}
|
||||||
|
indexPattern={indexPattern}
|
||||||
|
isInPanelView
|
||||||
|
/>
|
||||||
|
</ExpandableSection>
|
||||||
|
<EuiHorizontalRule margin="l" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{scheduleRuleData && (
|
||||||
|
<>
|
||||||
|
<ExpandableSection
|
||||||
|
title={i18n.RULE_PREVIEW_SCHEDULE_TEXT}
|
||||||
|
expanded={false}
|
||||||
|
data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID}
|
||||||
|
>
|
||||||
|
<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>
|
</EuiPanel>
|
||||||
|
) : ruleLoading ? (
|
||||||
|
<EuiLoadingSpinner size="l" data-test-subj={RULE_PREVIEW_LOADING_TEST_ID} />
|
||||||
) : null;
|
) : 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 */
|
/* 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_BODY_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewBody';
|
||||||
export const RULE_PREVIEW_ABOUT_TEST_ID = `securitySolutionDocumentDetailsFlyoutRulePreviewAboutSection`;
|
export const RULE_PREVIEW_ABOUT_TEST_ID = `securitySolutionDocumentDetailsFlyoutRulePreviewAboutSection`;
|
||||||
export const RULE_PREVIEW_ABOUT_HEADER_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + HEADER_TEST_ID;
|
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_HEADER_TEST_ID = RULE_PREVIEW_SCHEDULE_TEST_ID + HEADER_TEST_ID;
|
||||||
export const RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID =
|
export const RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID =
|
||||||
RULE_PREVIEW_SCHEDULE_TEST_ID + 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_FOOTER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewFooter';
|
||||||
export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID = 'goToRuleDetails';
|
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',
|
'xpack.securitySolution.flyout.documentDetails.rulePreviewScheduleSectionText',
|
||||||
{ defaultMessage: 'Schedule' }
|
{ 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 React, { createContext, useContext, useMemo } from 'react';
|
||||||
|
import type { DataViewBase } from '@kbn/es-query';
|
||||||
import type { PreviewPanelProps } from '.';
|
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 {
|
export interface PreviewPanelContext {
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +30,10 @@ export interface PreviewPanelContext {
|
||||||
* Rule id if preview is rule details
|
* Rule id if preview is rule details
|
||||||
*/
|
*/
|
||||||
ruleId: string;
|
ruleId: string;
|
||||||
|
/**
|
||||||
|
* Index pattern for rule details
|
||||||
|
*/
|
||||||
|
indexPattern: DataViewBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PreviewPanelContext = createContext<PreviewPanelContext | undefined>(undefined);
|
export const PreviewPanelContext = createContext<PreviewPanelContext | undefined>(undefined);
|
||||||
|
@ -43,6 +52,12 @@ export const PreviewPanelProvider = ({
|
||||||
ruleId,
|
ruleId,
|
||||||
children,
|
children,
|
||||||
}: PreviewPanelProviderProps) => {
|
}: PreviewPanelProviderProps) => {
|
||||||
|
const [{ pageName }] = useRouteSpy();
|
||||||
|
const sourcererScope =
|
||||||
|
pageName === SecurityPageName.detections
|
||||||
|
? SourcererScopeName.detections
|
||||||
|
: SourcererScopeName.default;
|
||||||
|
const sourcererDataView = useSourcererDataView(sourcererScope);
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
() =>
|
() =>
|
||||||
id && indexName && scopeId
|
id && indexName && scopeId
|
||||||
|
@ -51,9 +66,10 @@ export const PreviewPanelProvider = ({
|
||||||
indexName,
|
indexName,
|
||||||
scopeId,
|
scopeId,
|
||||||
ruleId: ruleId ?? '',
|
ruleId: ruleId ?? '',
|
||||||
|
indexPattern: sourcererDataView.indexPattern,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
[id, indexName, scopeId, ruleId]
|
[id, indexName, scopeId, ruleId, sourcererDataView.indexPattern]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { css } from '@emotion/react';
|
|
||||||
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
|
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { panels } from './panels';
|
import { panels } from './panels';
|
||||||
|
@ -21,7 +20,6 @@ export interface PreviewPanelProps extends FlyoutPanelProps {
|
||||||
id: string;
|
id: string;
|
||||||
indexName: string;
|
indexName: string;
|
||||||
scopeId: string;
|
scopeId: string;
|
||||||
banner?: string;
|
|
||||||
ruleId?: string;
|
ruleId?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -38,14 +36,13 @@ export const PreviewPanel: React.FC<Partial<PreviewPanelProps>> = memo(({ path }
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup justifyContent="spaceBetween" direction="column" className="eui-fullHeight">
|
<EuiFlexGroup
|
||||||
<EuiFlexItem
|
justifyContent="spaceBetween"
|
||||||
css={css`
|
direction="column"
|
||||||
margin-top: -15px;
|
gutterSize="none"
|
||||||
`}
|
style={{ height: '100%' }}
|
||||||
>
|
>
|
||||||
{previewPanel.content}
|
<EuiFlexItem style={{ marginTop: '-15px' }}>{previewPanel.content}</EuiFlexItem>
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>{previewPanel.footer}</EuiFlexItem>
|
<EuiFlexItem grow={false}>{previewPanel.footer}</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,4 +15,5 @@ export const mockContextValue: PreviewPanelContext = {
|
||||||
indexName: 'index',
|
indexName: 'index',
|
||||||
scopeId: 'scopeId',
|
scopeId: 'scopeId',
|
||||||
ruleId: '',
|
ruleId: '',
|
||||||
|
indexPattern: { fields: [], title: 'test index' },
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ export type PreviewPanelType = Array<{
|
||||||
/**
|
/**
|
||||||
* Footer section in the panel
|
* Footer section in the panel
|
||||||
*/
|
*/
|
||||||
footer: React.ReactElement;
|
footer?: React.ReactElement;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -61,7 +61,7 @@ export const Description: FC = () => {
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButtonEmpty
|
<EuiButtonEmpty
|
||||||
size="s"
|
size="s"
|
||||||
iconType="arrowRight"
|
iconType="expand"
|
||||||
onClick={openRulePreview}
|
onClick={openRulePreview}
|
||||||
iconSide="right"
|
iconSide="right"
|
||||||
data-test-subj={RULE_SUMMARY_BUTTON_TEST_ID}
|
data-test-subj={RULE_SUMMARY_BUTTON_TEST_ID}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue