[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
5 changed files with 60 additions and 37 deletions
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue