[Security Solution] Fixes the Read Less button rendering below the fold (#96524)

## [Security Solution] Fixes the `Read Less` button rendering below the fold

Fixes issue <https://github.com/elastic/kibana/issues/95295> where the `Read Less` button in the Event Details flyout is rendered below the fold when an event's `message` field is too large, per the `Before` and `After` screenshots below:

### Before

![before](https://user-images.githubusercontent.com/4459398/113962310-aa463e80-97e4-11eb-93f9-f4a90bd250f6.png)

_Before: The `Read Less` button is not visible in the `Event details` flyout above_

### After

![after](https://user-images.githubusercontent.com/4459398/113962433-e9748f80-97e4-11eb-8f46-835eb12ea09d.png)

_After: The `Read Less` button is visible in the `Event details` flyout above_

In the _After_ screenshot above, the long `message` is rendered in a vertically scrollable view that occupies ~ one third of the vertical height of the viewport. The `Read Less` button is visible below the message.

### Desk Testing

Desk tested on a 16" MBP, and at larger desktop resolutions in:

- Chrome `89.0.4389.114`
- Firefox `87.0`
- Safari `14.0.3`
This commit is contained in:
Andrew Goldstein 2021-04-08 12:50:31 -06:00 committed by GitHub
parent 4af344a9b0
commit e871e84365
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 178 additions and 4 deletions

View file

@ -0,0 +1,163 @@
/*
* 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 { mount } from 'enzyme';
import { repeat } from 'lodash/fp';
import React from 'react';
import { LineClamp } from '.';
describe('LineClamp', () => {
const message = repeat(1000, 'abcdefghij '); // 10 characters, with a trailing space
describe('no overflow', () => {
test('it does NOT render the expanded line clamp when isOverflow is falsy', () => {
const wrapper = mount(<LineClamp content={message} />);
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} />);
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} />);
expect(wrapper.find('[data-test-subj="default-line-clamp"]').first().text()).toBe(message);
});
test('it does NOT render the `Read More` button when isOverflow is falsy', () => {
const wrapper = mount(<LineClamp content={message} />);
expect(wrapper.find('[data-test-subj="summary-view-readmore"]').exists()).toBe(false);
});
});
describe('overflow', () => {
const clientHeight = 400;
const scrollHeight = clientHeight + 100; // scrollHeight is > clientHeight
beforeAll(() => {
Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
configurable: true,
value: clientHeight,
});
Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
configurable: true,
value: scrollHeight,
});
});
test('it does NOT render the expanded line clamp by default when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
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} />);
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} />);
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} />);
expect(wrapper.find('[data-test-subj="summary-view-readmore"]').first().text()).toBe(
'Read More'
);
});
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} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="summary-view-readmore"]').first().text()).toBe(
'Read Less'
);
});
test('it renders the expanded content after the user clicks the `Read More` button when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').first().text()).toBe(message);
});
});
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} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').first()).toHaveStyleRule(
'max-height',
'33vh'
);
});
test('it automatically vertically scrolls the content when isOverflow is true', () => {
const wrapper = mount(<LineClamp content={message} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').first()).toHaveStyleRule(
'overflow-y',
'auto'
);
});
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} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="styled-line-clamp"]').exists()).toBe(false);
});
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} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="default-line-clamp"]').exists()).toBe(false);
});
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} />);
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update(); // 1st toggle
wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click');
wrapper.update(); // 2nd toggle
expect(wrapper.find('[data-test-subj="summary-view-readmore"]').first().text()).toBe(
'Read More' // after the 2nd toggle, the button once-again says `Read More`
);
});
});
});

View file

@ -11,7 +11,7 @@ import styled from 'styled-components';
import * as i18n from './translations';
const LINE_CLAMP = 3;
const LINE_CLAMP_HEIGHT = 4.5;
const LINE_CLAMP_HEIGHT = 5.5;
const StyledLineClamp = styled.div`
display: -webkit-box;
@ -28,6 +28,13 @@ const ReadMore = styled(EuiButtonEmpty)`
}
`;
const ExpandedContent = styled.div`
max-height: 33vh;
overflow-wrap: break-word;
overflow-x: hidden;
overflow-y: auto;
`;
const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) => {
const [isOverflow, setIsOverflow] = useState<boolean | null>(null);
const [isExpanded, setIsExpanded] = useState<boolean | null>(null);
@ -60,11 +67,15 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content })
return (
<>
{isExpanded ? (
<p>{content}</p>
<ExpandedContent data-test-subj="expanded-line-clamp">
<p>{content}</p>
</ExpandedContent>
) : isOverflow == null || isOverflow === true ? (
<StyledLineClamp ref={descriptionRef}>{content}</StyledLineClamp>
<StyledLineClamp data-test-subj="styled-line-clamp" ref={descriptionRef}>
{content}
</StyledLineClamp>
) : (
<EuiText>{content}</EuiText>
<EuiText data-test-subj="default-line-clamp">{content}</EuiText>
)}
{isOverflow && (
<ReadMore onClick={toggleReadMore} size="s" data-test-subj="summary-view-readmore">