[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:
parent
2010ec6ac4
commit
ebfba81ba5
|
@ -177,7 +177,7 @@ export function mockTreeWithNoAncestorsAnd2Children({
|
|||
const origin: ResolverEvent = mockEndpointEvent({
|
||||
pid: 0,
|
||||
entityID: originID,
|
||||
name: 'c',
|
||||
name: 'c.ext',
|
||||
parentEntityId: 'none',
|
||||
timestamp: 0,
|
||||
});
|
||||
|
|
|
@ -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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue