[Security Solution][Detections] Fix Investigation guide format issues (#101609)

* Fix 'Detection' / 'Investigation Guide' UI broken when it contains long words

* Fix investigation guide is not formatted under Alert details flyout

* Add LineClamp to investigation guide field
* It enhances LineClamp to support a react node instead of only text

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Pablo Machado 2021-06-29 17:00:30 +02:00 committed by GitHub
parent 24661fe208
commit 6ee79558ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 39 deletions

View file

@ -36,6 +36,7 @@ import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../networ
import { SummaryView } from './summary_view';
import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers';
import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback';
import { MarkdownRenderer } from '../markdown_editor';
import { LineClamp } from '../line_clamp';
import { endpointAlertCheck } from '../../utils/endpoint_alert_check';
@ -221,7 +222,9 @@ const AlertSummaryViewComponent: React.FC<{
<StyledEuiDescriptionList data-test-subj={`summary-view-guide`} compressed>
<EuiDescriptionListTitle>{i18n.INVESTIGATION_GUIDE}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<LineClamp content={maybeRule?.note} />
<LineClamp>
<MarkdownRenderer>{maybeRule.note}</MarkdownRenderer>
</LineClamp>
</EuiDescriptionListDescription>
</StyledEuiDescriptionList>
)}

View file

@ -16,25 +16,30 @@ describe('LineClamp', () => {
describe('no overflow', () => {
test('it does NOT render the expanded line clamp when isOverflow is falsy', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').exists()).toBe(false);
});
test('it does NOT render the styled line clamp expanded when isOverflow is falsy', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="styled-line-clamp"]').exists()).toBe(false);
});
test('it renders the default line clamp when isOverflow is falsy', () => {
const wrapper = mount(<LineClamp content={message} />);
test('it renders the children when isOverflow is falsy', () => {
const TestComponent = () => <>{message}</>;
const wrapper = mount(
<LineClamp>
<TestComponent />
</LineClamp>
);
expect(wrapper.find('[data-test-subj="default-line-clamp"]').first().text()).toBe(message);
expect(wrapper.childAt(0).type()).toBe(TestComponent);
});
test('it does NOT render the `Read More` button when isOverflow is falsy', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="summary-view-readmore"]').exists()).toBe(false);
});
@ -59,25 +64,25 @@ describe('LineClamp', () => {
});
test('it does NOT render the expanded line clamp by default when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').exists()).toBe(false);
});
test('it renders the styled line clamp when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="styled-line-clamp"]').first().text()).toBe(message);
});
test('it does NOT render the default line clamp when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="default-line-clamp"]').exists()).toBe(false);
});
test('it renders the `Read More` button with the expected (default) text when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
expect(wrapper.find('[data-test-subj="summary-view-readmore"]').first().text()).toBe(
'Read More'
@ -86,7 +91,7 @@ describe('LineClamp', () => {
describe('clicking the Read More button', () => {
test('it displays the `Read Less` button text after the user clicks the `Read More` button when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
@ -97,7 +102,7 @@ describe('LineClamp', () => {
});
test('it renders the expanded content after the user clicks the `Read More` button when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
@ -107,7 +112,7 @@ describe('LineClamp', () => {
});
test('it renders the expanded content with a max-height of one third the view height when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
@ -119,7 +124,7 @@ describe('LineClamp', () => {
});
test('it automatically vertically scrolls the content when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
@ -131,7 +136,7 @@ describe('LineClamp', () => {
});
test('it does NOT render the styled line clamp after the user clicks the `Read More` button when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
@ -140,7 +145,7 @@ describe('LineClamp', () => {
});
test('it does NOT render the default line clamp after the user clicks the `Read More` button when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
@ -149,7 +154,7 @@ describe('LineClamp', () => {
});
test('it once again displays the `Read More` button text after the user clicks the `Read Less` when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
const wrapper = mount(<LineClamp>{message}</LineClamp>);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update(); // 1st toggle

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { EuiButtonEmpty, EuiText } from '@elastic/eui';
import React, { useRef, useState, useEffect, useCallback } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import React, { useRef, useState, useEffect, useCallback, ReactNode } from 'react';
import styled from 'styled-components';
import * as i18n from './translations';
@ -36,9 +36,9 @@ const StyledLineClamp = styled.div<{ lineClampHeight: number }>`
`;
const LineClampComponent: React.FC<{
content?: string | null;
children: ReactNode;
lineClampHeight?: number;
}> = ({ content, lineClampHeight = LINE_CLAMP_HEIGHT }) => {
}> = ({ children, lineClampHeight = LINE_CLAMP_HEIGHT }) => {
const [isOverflow, setIsOverflow] = useState<boolean | null>(null);
const [isExpanded, setIsExpanded] = useState<boolean | null>(null);
const descriptionRef = useRef<HTMLDivElement>(null);
@ -47,7 +47,7 @@ const LineClampComponent: React.FC<{
}, []);
useEffect(() => {
if (content != null && descriptionRef?.current?.clientHeight != null) {
if (descriptionRef?.current?.clientHeight != null) {
if (
(descriptionRef?.current?.scrollHeight ?? 0) > (descriptionRef?.current?.clientHeight ?? 0)
) {
@ -55,38 +55,44 @@ const LineClampComponent: React.FC<{
}
if (
((content == null || descriptionRef?.current?.scrollHeight) ?? 0) <=
(descriptionRef?.current?.clientHeight ?? 0)
(descriptionRef?.current?.scrollHeight ?? 0) <= (descriptionRef?.current?.clientHeight ?? 0)
) {
setIsOverflow(false);
}
}
}, [content]);
}, []);
if (!content) {
return null;
if (isExpanded) {
return (
<>
<ExpandedContent data-test-subj="expanded-line-clamp">
<p>{children}</p>
</ExpandedContent>
{isOverflow && (
<ReadMore onClick={toggleReadMore} size="s" data-test-subj="summary-view-readmore">
{i18n.READ_LESS}
</ReadMore>
)}
</>
);
}
return (
<>
{isExpanded ? (
<ExpandedContent data-test-subj="expanded-line-clamp">
<p>{content}</p>
</ExpandedContent>
) : isOverflow == null || isOverflow === true ? (
{isOverflow == null || isOverflow === true ? (
<StyledLineClamp
data-test-subj="styled-line-clamp"
ref={descriptionRef}
lineClampHeight={lineClampHeight}
>
{content}
{children}
</StyledLineClamp>
) : (
<EuiText data-test-subj="default-line-clamp">{content}</EuiText>
children
)}
{isOverflow && (
<ReadMore onClick={toggleReadMore} size="s" data-test-subj="summary-view-readmore">
{isExpanded ? i18n.READ_LESS : i18n.READ_MORE}
{i18n.READ_MORE}
</ReadMore>
)}
</>

View file

@ -37,6 +37,7 @@ const FlexGroupFullHeight = styled(EuiFlexGroup)`
const VerticalOverflowContainer = styled.div((props: { maxHeight: number }) => ({
'max-height': `${props.maxHeight}px`,
'overflow-y': 'hidden',
'word-break': 'break-word',
}));
const VerticalOverflowContent = styled.div((props: { maxHeight: number }) => ({

View file

@ -206,11 +206,12 @@ const TimelineDescriptionComponent: React.FC<FlyoutHeaderProps> = ({ timelineId
const description = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).description
);
return (
<EuiText size="s" data-test-subj="timeline-description">
{description.length ? (
<LineClamp key={description.length} content={description} lineClampHeight={4.5} />
<LineClamp key={description.length} lineClampHeight={4.5}>
{description}
</LineClamp>
) : (
commonI18n.DESCRIPTION
)}

View file

@ -113,7 +113,7 @@ export const ExpandableEvent = React.memo<Props>(
<EuiDescriptionList data-test-subj="event-message" compressed>
<EuiDescriptionListTitle>{i18n.MESSAGE}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<LineClamp content={message} />
<LineClamp>{message}</LineClamp>
</EuiDescriptionListDescription>
</EuiDescriptionList>
<EuiSpacer size="m" />