[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({
pid: 0,
entityID: originID,
name: 'c',
name: 'c.ext',
parentEntityId: 'none',
timestamp: 0,
});

View file

@ -81,7 +81,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
};
})
).toYieldEqualTo({
title: 'c',
title: 'c.ext',
titleIcon: 'Running Process',
detailEntries: [
['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, {
@ -174,7 +187,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
.testSubject('resolver:node-list:node-link:title')
.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
* 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 * as selectors from '../../store/selectors';
import * as event from '../../../../common/endpoint/models/event';
import { formatDate, StyledBreadcrumbs } from './panel_content_utilities';
import { formatDate, StyledBreadcrumbs, GeneratedText } from './panel_content_utilities';
import {
processPath,
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:
* Created, PID, User/Domain, etc.
@ -114,7 +118,7 @@ export const ProcessDetails = memo(function ProcessDetails({
.map((entry) => {
return {
...entry,
description: String(entry.description),
description: <GeneratedText>{String(entry.description)}</GeneratedText>,
};
});
@ -163,13 +167,15 @@ export const ProcessDetails = memo(function ProcessDetails({
<StyledBreadcrumbs breadcrumbs={crumbs} />
<EuiSpacer size="l" />
<EuiTitle size="xs">
<h4 aria-describedby={titleID}>
<StyledTitle aria-describedby={titleID}>
<CubeForProcess
data-test-subj="resolver:node-detail:title-icon"
running={!isProcessTerminated}
/>
<span data-test-subj="resolver:node-detail:title">{processName}</span>
</h4>
<span data-test-subj="resolver:node-detail:title">
<GeneratedText>{processName}</GeneratedText>
</span>
</StyledTitle>
</EuiTitle>
<EuiText>
<EuiTextColor color="subdued">

View file

@ -10,7 +10,7 @@ import { EuiSpacer, EuiText, EuiDescriptionList, EuiTextColor, EuiTitle } from '
import styled from 'styled-components';
import { useSelector } from 'react-redux';
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 { ResolverEvent } from '../../../../common/endpoint/types';
import * as selectors from '../../store/selectors';
@ -57,34 +57,6 @@ const TitleHr = memo(() => {
});
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
* seeding with `<wbr />` tags.