Skip to content

Commit

Permalink
[Security Solution] Expandable flyout - Rule preview contents (#163027)
Browse files Browse the repository at this point in the history
## Summary

This PR is part 2 of adding a rule preview panel to the expandable
flyout. PR (#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


![image](https://github.com/elastic/kibana/assets/18648970/bbccbec6-f5f2-4ac5-8715-9caf357283ee)

**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
  • Loading branch information
christineweng authored Aug 10, 2023
1 parent 37a53b6 commit c28cb61
Show file tree
Hide file tree
Showing 24 changed files with 572 additions and 125 deletions.
34 changes: 13 additions & 21 deletions packages/kbn-expandable-flyout/src/components/preview_section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -124,28 +123,21 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
);

return (
<>
<div
css={css`
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: ${left};
background-color: ${euiTheme.colors.shadow};
opacity: 0.5;
`}
/>
<div
css={css`
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: ${left};
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}
Expand All @@ -162,7 +154,7 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
</EuiSplitPanel.Inner>
<EuiSplitPanel.Inner paddingSize="none">{component}</EuiSplitPanel.Inner>
</EuiSplitPanel.Outer>
</>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -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');
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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} />
) : (
''
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}
Expand All @@ -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>
Expand Down Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -68,18 +68,27 @@ 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,>({
data,
columns = 'multi',
indexPatterns,
schema,
isInPanelView,
}: StepRuleDescriptionProps<T>) => {
const kibana = useKibana();
const license = useLicense();
Expand Down Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
);
});
});
});
Loading

0 comments on commit c28cb61

Please sign in to comment.