[Security Solution][Resolver] break/wrap for process detail (#76095)

* [Security Solution][Resolver]break/wrap for process detail

* add an enzyme test to check for the breakers
This commit is contained in:
Brent Kimmel 2020-08-27 13:25:01 -04:00 committed by GitHub
parent 2010ec6ac4
commit ebfba81ba5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 37 deletions

View file

@ -177,7 +177,7 @@ export function mockTreeWithNoAncestorsAnd2Children({
const origin: ResolverEvent = mockEndpointEvent({ const origin: ResolverEvent = mockEndpointEvent({
pid: 0, pid: 0,
entityID: originID, entityID: originID,
name: 'c', name: 'c.ext',
parentEntityId: 'none', parentEntityId: 'none',
timestamp: 0, timestamp: 0,
}); });

View file

@ -81,7 +81,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
}; };
}) })
).toYieldEqualTo({ ).toYieldEqualTo({
title: 'c', title: 'c.ext',
titleIcon: 'Running Process', titleIcon: 'Running Process',
detailEntries: [ detailEntries: [
['process.executable', 'executable'], ['process.executable', 'executable'],
@ -94,6 +94,19 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
], ],
}); });
}); });
it('should have breaking opportunities (<wbr>s) in node titles to allow wrapping', async () => {
await expect(
simulator().map(() => {
const titleWrapper = simulator().testSubject('resolver:node-detail:title');
return {
wordBreaks: titleWrapper.find('wbr').length,
};
})
).toYieldEqualTo({
// The GeneratedText component adds 1 <wbr> after the period and one at the end
wordBreaks: 2,
});
});
}); });
const queryStringWithFirstChildSelected = urlSearch(resolverComponentInstanceID, { const queryStringWithFirstChildSelected = urlSearch(resolverComponentInstanceID, {
@ -174,7 +187,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
.testSubject('resolver:node-list:node-link:title') .testSubject('resolver:node-list:node-link:title')
.map((node) => node.text()); .map((node) => node.text());
}) })
).toYieldEqualTo(['c', 'd', 'e']); ).toYieldEqualTo(['c.ext', 'd', 'e']);
}); });
}); });
}); });

View file

@ -43,6 +43,38 @@ const betaBadgeLabel = i18n.translate(
} }
); );
/**
* A component that renders an element with breaking opportunities (`<wbr>`s)
* spliced into text children at word boundaries.
*/
export const GeneratedText = React.memo(function ({ children }) {
return <>{processedValue()}</>;
function processedValue() {
return React.Children.map(children, (child) => {
if (typeof child === 'string') {
const valueSplitByWordBoundaries = child.split(/\b/);
if (valueSplitByWordBoundaries.length < 2) {
return valueSplitByWordBoundaries[0];
}
return [
valueSplitByWordBoundaries[0],
...valueSplitByWordBoundaries
.splice(1)
.reduce(function (generatedTextMemo: Array<string | JSX.Element>, value, index) {
return [...generatedTextMemo, value, <wbr />];
}, []),
];
} else {
return child;
}
});
}
});
GeneratedText.displayName = 'GeneratedText';
/** /**
* A component to keep time representations in blocks so they don't wrap * A component to keep time representations in blocks so they don't wrap
* and look bad. * and look bad.

View file

@ -19,7 +19,7 @@ import { FormattedMessage } from 'react-intl';
import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
import * as selectors from '../../store/selectors'; import * as selectors from '../../store/selectors';
import * as event from '../../../../common/endpoint/models/event'; import * as event from '../../../../common/endpoint/models/event';
import { formatDate, StyledBreadcrumbs } from './panel_content_utilities'; import { formatDate, StyledBreadcrumbs, GeneratedText } from './panel_content_utilities';
import { import {
processPath, processPath,
processPid, processPid,
@ -39,6 +39,10 @@ const StyledDescriptionList = styled(EuiDescriptionList)`
} }
`; `;
const StyledTitle = styled('h4')`
overflow-wrap: break-word;
`;
/** /**
* A description list view of all the Metadata that goes with a particular process event, like: * A description list view of all the Metadata that goes with a particular process event, like:
* Created, PID, User/Domain, etc. * Created, PID, User/Domain, etc.
@ -114,7 +118,7 @@ export const ProcessDetails = memo(function ProcessDetails({
.map((entry) => { .map((entry) => {
return { return {
...entry, ...entry,
description: String(entry.description), description: <GeneratedText>{String(entry.description)}</GeneratedText>,
}; };
}); });
@ -163,13 +167,15 @@ export const ProcessDetails = memo(function ProcessDetails({
<StyledBreadcrumbs breadcrumbs={crumbs} /> <StyledBreadcrumbs breadcrumbs={crumbs} />
<EuiSpacer size="l" /> <EuiSpacer size="l" />
<EuiTitle size="xs"> <EuiTitle size="xs">
<h4 aria-describedby={titleID}> <StyledTitle aria-describedby={titleID}>
<CubeForProcess <CubeForProcess
data-test-subj="resolver:node-detail:title-icon" data-test-subj="resolver:node-detail:title-icon"
running={!isProcessTerminated} running={!isProcessTerminated}
/> />
<span data-test-subj="resolver:node-detail:title">{processName}</span> <span data-test-subj="resolver:node-detail:title">
</h4> <GeneratedText>{processName}</GeneratedText>
</span>
</StyledTitle>
</EuiTitle> </EuiTitle>
<EuiText> <EuiText>
<EuiTextColor color="subdued"> <EuiTextColor color="subdued">

View file

@ -10,7 +10,7 @@ import { EuiSpacer, EuiText, EuiDescriptionList, EuiTextColor, EuiTitle } from '
import styled from 'styled-components'; import styled from 'styled-components';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { StyledBreadcrumbs, BoldCode, StyledTime } from './panel_content_utilities'; import { StyledBreadcrumbs, BoldCode, StyledTime, GeneratedText } from './panel_content_utilities';
import * as event from '../../../../common/endpoint/models/event'; import * as event from '../../../../common/endpoint/models/event';
import { ResolverEvent } from '../../../../common/endpoint/types'; import { ResolverEvent } from '../../../../common/endpoint/types';
import * as selectors from '../../store/selectors'; import * as selectors from '../../store/selectors';
@ -57,34 +57,6 @@ const TitleHr = memo(() => {
}); });
TitleHr.displayName = 'TitleHR'; TitleHr.displayName = 'TitleHR';
const GeneratedText = React.memo(function ({ children }) {
return <>{processedValue()}</>;
function processedValue() {
return React.Children.map(children, (child) => {
if (typeof child === 'string') {
const valueSplitByWordBoundaries = child.split(/\b/);
if (valueSplitByWordBoundaries.length < 2) {
return valueSplitByWordBoundaries[0];
}
return [
valueSplitByWordBoundaries[0],
...valueSplitByWordBoundaries
.splice(1)
.reduce(function (generatedTextMemo: Array<string | JSX.Element>, value, index) {
return [...generatedTextMemo, value, <wbr />];
}, []),
];
} else {
return child;
}
});
}
});
GeneratedText.displayName = 'GeneratedText';
/** /**
* Take description list entries and prepare them for display by * Take description list entries and prepare them for display by
* seeding with `<wbr />` tags. * seeding with `<wbr />` tags.