Skip to content

Commit de7c822

Browse files
gggritsoJonasBagetsantry[bot]
authored
ref(ui): Add ellipsize string helper (#94935)
Was surprised that this helper doesn't exist? Maybe it does, but somewhere I didn't see it? Anyway I added a new one, y'all let me know what you want to call this thing, and if all the behaviour makes sense. --------- Co-authored-by: Jonas <jonas.badalic@sentry.io> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent ea7e5c1 commit de7c822

File tree

5 files changed

+71
-11
lines changed

5 files changed

+71
-11
lines changed

static/app/components/events/breadcrumbs/breadcrumbItemContent.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
type RawCrumb,
1616
} from 'sentry/types/breadcrumbs';
1717
import {defined} from 'sentry/utils';
18+
import {ellipsize} from 'sentry/utils/string/ellipsize';
1819
import {isUrl} from 'sentry/utils/string/isUrl';
1920
import {usePrismTokens} from 'sentry/utils/usePrismTokens';
2021

@@ -55,11 +56,7 @@ export default function BreadcrumbItemContent({
5556
/>
5657
) : (
5758
<StructuredData
58-
value={
59-
bc.message.length > MESSAGE_PREVIEW_CHAR_LIMIT
60-
? bc.message.substring(0, MESSAGE_PREVIEW_CHAR_LIMIT) + '\u2026'
61-
: bc.message
62-
}
59+
value={ellipsize(bc.message, MESSAGE_PREVIEW_CHAR_LIMIT)}
6360
// Note: Annotations applying to trimmed content will not be applied.
6461
meta={meta?.message}
6562
{...structuredDataProps}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {ellipsize} from 'sentry/utils/string/ellipsize';
2+
3+
describe('ellipsize', () => {
4+
it.each([
5+
['hello', 10, 'hello'],
6+
['test', 5, 'test'],
7+
['a', 2, 'a'],
8+
['hello', 5, 'hello'],
9+
['hello', Infinity, 'hello'],
10+
['test', 4, 'test'],
11+
['a', 1, 'a'],
12+
['hello world', 8, 'hello wo…'],
13+
['this is a long string', 10, 'this is a…'],
14+
['abcdefghijk', 5, 'abcde…'],
15+
['hello', 1, 'h…'],
16+
['ab', 1, 'a…'],
17+
['abc', 1, 'a…'],
18+
['abc', 0, '…'],
19+
['hello there', 6, 'hello…'],
20+
[' hello there', 6, ' hell…'],
21+
['hello@world.com', 10, 'hello@worl…'],
22+
['line1\nline2\nline3', 12, 'line1\nline2…'],
23+
['café', 3, 'caf…'],
24+
['résumé', 5, 'résum…'],
25+
['👋 hello world', 8, '👋 hello…'],
26+
[' ', 7, ' '],
27+
[' ', 2, ' …'],
28+
[' hello ', 6, ' hell…'],
29+
['\t\n\r', 2, '\t\n…'],
30+
])('should truncate "%s" with maxLength %d to "%s"', (input, maxLength, expected) => {
31+
expect(ellipsize(input, maxLength)).toBe(expected);
32+
});
33+
34+
it.each([[NaN], [-Infinity], [-5]])('throws an error if the input is %s', input => {
35+
expect(() => {
36+
ellipsize('string', input);
37+
}).toThrow();
38+
});
39+
});

static/app/utils/string/ellipsize.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Truncates a string to a maximum length and adds an ellipsis character (…) if truncated. Similar to the Python `truncatechars`. Does not trim the string, please trim before calling this function. Does right-trim the string before output to avoid having whitespace followed by an ellipsis, unless the string is only whitespace.
3+
*
4+
* @example
5+
* ellipsize('hello world', 8) // returns 'hello wo…'
6+
* ellipsize('hello world', 6) // returns 'hello…'
7+
* ellipsize(' ', 6) // returns ''
8+
* ellipsize('short', 10) // returns 'short'
9+
*/
10+
export function ellipsize(str: string, length: number): string {
11+
if (length < 0 || isNaN(length))
12+
throw new TypeError(
13+
`Invalid string length argument value of ${length} provided to ellipsize`
14+
);
15+
16+
if (str.length <= length) return str;
17+
18+
// If the string is only whitespace, skip `trimRight` since trimming will completely erase the string. If the string was a long sequence of whitespace characters, that would return a loose ellipsis
19+
const trimmed = str.trim();
20+
if (trimmed.length === 0) return `${str.slice(0, length)}${ELLIPSIS}`;
21+
22+
// Otherwise, trim normally
23+
return `${str.slice(0, length).trimEnd()}${ELLIPSIS}`;
24+
}
25+
26+
const ELLIPSIS = `\u2026`;

static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
generateContinuousProfileFlamechartRouteWithQuery,
1111
generateProfileFlamechartRouteWithQuery,
1212
} from 'sentry/utils/profiling/routes';
13+
import {ellipsize} from 'sentry/utils/string/ellipsize';
1314
import useOrganization from 'sentry/utils/useOrganization';
1415
import useProjects from 'sentry/utils/useProjects';
1516
import {traceAnalytics} from 'sentry/views/performance/newTraceDetails/traceAnalytics';
@@ -128,9 +129,7 @@ export function TraceProfiles({tree}: {tree: TraceTree}) {
128129
<span>{node.value.op ?? '<unknown>'}</span>{' '}
129130
<span className="TraceDescription" title={node.value.description}>
130131
{node.value.description
131-
? node.value.description.length > 100
132-
? node.value.description.slice(0, 100).trim() + '\u2026'
133-
: node.value.description
132+
? ellipsize(node.value.description, 100)
134133
: (spanId ?? 'unknown')}
135134
</span>
136135
</Fragment>

static/app/views/performance/newTraceDetails/traceRow/traceSpanRow.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import {PlatformIcon} from 'platformicons';
33

4+
import {ellipsize} from 'sentry/utils/string/ellipsize';
45
import {isEAPSpanNode} from 'sentry/views/performance/newTraceDetails/traceGuards';
56
import {TraceIcons} from 'sentry/views/performance/newTraceDetails/traceIcons';
67
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
@@ -83,9 +84,7 @@ export function TraceSpanRow(
8384
<span className="TraceDescription" title={props.node.value.description}>
8485
{getNodeDescriptionPrefix(props.node)}
8586
{props.node.value.description
86-
? props.node.value.description.length > 100
87-
? props.node.value.description.slice(0, 100).trim() + '\u2026'
88-
: props.node.value.description
87+
? ellipsize(props.node.value.description, 100)
8988
: (spanId ?? 'unknown')}
9089
</span>
9190
</div>

0 commit comments

Comments
 (0)