Skip to content

Commit 0661d41

Browse files
authored
feat(explore): Format numeric tags nicely in explore (#81255)
Some tags may not have loaded or is from a different project. In thise case, we should try to render them nicely.
1 parent 5a93960 commit 0661d41

File tree

8 files changed

+123
-71
lines changed

8 files changed

+123
-71
lines changed
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
import BadgeTag from 'sentry/components/badge/tag';
22
import {t} from 'sentry/locale';
3-
import type {Tag} from 'sentry/types/group';
43
import {defined} from 'sentry/utils';
54
import type {ParsedFunction} from 'sentry/utils/discover/fields';
65
import {FieldKind} from 'sentry/utils/fields';
76

87
interface TypeBadgeProps {
98
func?: ParsedFunction;
10-
tag?: Tag;
9+
kind?: FieldKind;
1110
}
1211

13-
export function TypeBadge({func, tag}: TypeBadgeProps) {
12+
export function TypeBadge({func, kind}: TypeBadgeProps) {
1413
if (defined(func)) {
1514
return <BadgeTag type="warning">{t('aggregation')}</BadgeTag>;
1615
}
1716

18-
if (tag?.kind === FieldKind.MEASUREMENT) {
17+
if (kind === FieldKind.MEASUREMENT) {
1918
return <BadgeTag type="success">{t('number')}</BadgeTag>;
2019
}
21-
if (tag?.kind === FieldKind.TAG) {
20+
21+
if (kind === FieldKind.TAG) {
2222
return <BadgeTag type="highlight">{t('string')}</BadgeTag>;
2323
}
24+
2425
return null;
2526
}

static/app/views/explore/tables/columnEditorModal.spec.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,17 @@ describe('ColumnEditorModal', function () {
128128
expect(column).toHaveTextContent(columns2[i]);
129129
});
130130

131-
const options = ['id', 'project', 'span.duration', 'span.op'];
131+
const options: [string, 'string' | 'number'][] = [
132+
['id', 'string'],
133+
['project', 'string'],
134+
['span.duration', 'number'],
135+
['span.op', 'string'],
136+
];
132137
await userEvent.click(screen.getByRole('button', {name: 'Column None'}));
133138
const columnOptions = await screen.findAllByRole('option');
134139
columnOptions.forEach((option, i) => {
135-
expect(option).toHaveTextContent(options[i]);
140+
expect(option).toHaveTextContent(options[i][0]);
141+
expect(option).toHaveTextContent(options[i][1]);
136142
});
137143

138144
await userEvent.click(columnOptions[3]);
@@ -170,11 +176,17 @@ describe('ColumnEditorModal', function () {
170176
expect(column).toHaveTextContent(columns1[i]);
171177
});
172178

173-
const options = ['id', 'project', 'span.duration', 'span.op'];
174-
await userEvent.click(screen.getByRole('button', {name: 'Column project'}));
179+
const options: [string, 'string' | 'number'][] = [
180+
['id', 'string'],
181+
['project', 'string'],
182+
['span.duration', 'number'],
183+
['span.op', 'string'],
184+
];
185+
await userEvent.click(screen.getByRole('button', {name: 'Column project string'}));
175186
const columnOptions = await screen.findAllByRole('option');
176187
columnOptions.forEach((option, i) => {
177-
expect(option).toHaveTextContent(options[i]);
188+
expect(option).toHaveTextContent(options[i][0]);
189+
expect(option).toHaveTextContent(options[i][1]);
178190
});
179191

180192
await userEvent.click(columnOptions[3]);

static/app/views/explore/tables/columnEditorModal.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {t} from 'sentry/locale';
1616
import {space} from 'sentry/styles/space';
1717
import type {TagCollection} from 'sentry/types/group';
1818
import {defined} from 'sentry/utils';
19+
import {FieldKind} from 'sentry/utils/fields';
1920
import {TypeBadge} from 'sentry/views/explore/components/typeBadge';
2021

2122
import {DragNDropContext} from '../contexts/dragNDropContext';
@@ -45,15 +46,15 @@ export function ColumnEditorModal({
4546
label: tag.name,
4647
value: tag.key,
4748
textValue: tag.name,
48-
trailingItems: <TypeBadge tag={tag} />,
49+
trailingItems: <TypeBadge kind={FieldKind.TAG} />,
4950
};
5051
}),
5152
...Object.values(numberTags).map(tag => {
5253
return {
5354
label: tag.name,
5455
value: tag.key,
5556
textValue: tag.name,
56-
trailingItems: <TypeBadge tag={tag} />,
57+
trailingItems: <TypeBadge kind={FieldKind.MEASUREMENT} />,
5758
};
5859
}),
5960
];

static/app/views/explore/tables/spansTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {t} from 'sentry/locale';
1111
import type {NewQuery} from 'sentry/types/organization';
1212
import {defined} from 'sentry/utils';
1313
import EventView from 'sentry/utils/discover/eventView';
14-
import {fieldAlignment} from 'sentry/utils/discover/fields';
14+
import {fieldAlignment, prettifyTagKey} from 'sentry/utils/discover/fields';
1515
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
1616
import useOrganization from 'sentry/utils/useOrganization';
1717
import usePageFilters from 'sentry/utils/usePageFilters';
@@ -151,7 +151,7 @@ export function SpansTable({setError}: SpansTableProps) {
151151
isFirst={i === 0}
152152
onClick={updateSort}
153153
>
154-
<span>{tag?.name ?? field}</span>
154+
<span>{tag?.name ?? prettifyTagKey(field)}</span>
155155
{defined(direction) && (
156156
<IconArrow
157157
size="xs"

static/app/views/explore/toolbar/index.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,9 @@ describe('ExploreToolbar', function () {
219219
const fields = [
220220
'id',
221221
'project',
222-
'span.op',
223222
'span.description',
224223
'span.duration',
224+
'span.op',
225225
'timestamp',
226226
];
227227
await userEvent.click(within(section).getByRole('button', {name: 'timestamp'}));

static/app/views/explore/toolbar/toolbarGroupBy.tsx

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,34 +39,26 @@ export function ToolbarGroupBy({disabled}: ToolbarGroupByProps) {
3939
const {groupBys, setGroupBys} = useGroupBys();
4040

4141
const options: SelectOption<string>[] = useMemo(() => {
42-
// These options aren't known to exist on this project but it was inserted into
43-
// the group bys somehow so it should be a valid options in the group bys.
44-
//
45-
// One place this may come from is when switching projects/environment/date range,
46-
// a tag may disappear based on the selection.
47-
const unknownOptions = groupBys
48-
.filter(groupBy => groupBy && !tags.hasOwnProperty(groupBy))
49-
.map(groupBy => {
50-
return {
51-
label: groupBy,
52-
value: groupBy,
53-
textValue: groupBy,
54-
};
55-
});
56-
57-
const knownOptions = Object.keys(tags).map(tagKey => {
58-
return {
59-
label: tagKey,
60-
value: tagKey,
61-
textValue: tagKey,
62-
};
63-
});
42+
const potentialOptions = [
43+
...Object.keys(tags),
44+
45+
// These options aren't known to exist on this project but it was inserted into
46+
// the group bys somehow so it should be a valid options in the group bys.
47+
//
48+
// One place this may come from is when switching projects/environment/date range,
49+
// a tag may disappear based on the selection.
50+
...groupBys.filter(groupBy => groupBy && !tags.hasOwnProperty(groupBy)),
51+
];
52+
potentialOptions.sort();
6453

6554
return [
6655
// hard code in an empty option
6756
{label: t('None'), value: '', textValue: t('none')},
68-
...unknownOptions,
69-
...knownOptions,
57+
...potentialOptions.map(key => ({
58+
label: key,
59+
value: key,
60+
textValue: key,
61+
})),
7062
];
7163
}, [groupBys, tags]);
7264

static/app/views/explore/toolbar/toolbarSortBy.tsx

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import {CompactSelect} from 'sentry/components/compactSelect';
66
import {Tooltip} from 'sentry/components/tooltip';
77
import {t} from 'sentry/locale';
88
import type {Sort} from 'sentry/utils/discover/fields';
9-
import {parseFunction, prettifyParsedFunction} from 'sentry/utils/discover/fields';
9+
import {
10+
parseFunction,
11+
prettifyParsedFunction,
12+
prettifyTagKey,
13+
TYPED_TAG_KEY_RE,
14+
} from 'sentry/utils/discover/fields';
15+
import {FieldKind} from 'sentry/utils/fields';
1016
import {TypeBadge} from 'sentry/views/explore/components/typeBadge';
1117
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
1218
import {useResultMode} from 'sentry/views/explore/hooks/useResultsMode';
@@ -32,36 +38,59 @@ export function ToolbarSortBy({fields, setSorts, sorts}: ToolbarSortByProps) {
3238
const stringTags = useSpanTags('string');
3339

3440
const fieldOptions: SelectOption<Field>[] = useMemo(() => {
35-
return fields.map(field => {
36-
const tag = stringTags[field] ?? numberTags[field] ?? null;
37-
if (tag) {
41+
const options = [
42+
...new Set(fields).keys().map(field => {
43+
const tag = stringTags[field] ?? numberTags[field] ?? null;
44+
if (tag) {
45+
return {
46+
label: tag.name,
47+
value: field,
48+
textValue: tag.name,
49+
trailingItems: <TypeBadge kind={tag?.kind} />,
50+
};
51+
}
52+
53+
const func = parseFunction(field);
54+
if (func) {
55+
const formatted = prettifyParsedFunction(func);
56+
return {
57+
label: formatted,
58+
value: field,
59+
textValue: formatted,
60+
trailingItems: <TypeBadge func={func} />,
61+
};
62+
}
63+
64+
const result = field.match(TYPED_TAG_KEY_RE);
65+
const kind =
66+
result?.[2] === 'string'
67+
? FieldKind.TAG
68+
: result?.[2] === 'number'
69+
? FieldKind.MEASUREMENT
70+
: undefined;
71+
3872
return {
39-
label: tag.name,
73+
label: prettifyTagKey(field),
4074
value: field,
41-
textValue: tag.name,
42-
trailingItems: <TypeBadge tag={tag} />,
75+
textValue: field,
76+
trailingItems: <TypeBadge kind={kind} />,
4377
};
78+
}),
79+
];
80+
81+
options.sort((a, b) => {
82+
if (a.label < b.label) {
83+
return -1;
4484
}
4585

46-
const func = parseFunction(field);
47-
if (func) {
48-
const formatted = prettifyParsedFunction(func);
49-
return {
50-
label: formatted,
51-
value: field,
52-
textValue: formatted,
53-
trailingItems: <TypeBadge func={func} />,
54-
};
86+
if (a.label > b.label) {
87+
return 1;
5588
}
5689

57-
// not a tag, maybe it's an aggregate
58-
return {
59-
label: field,
60-
value: field,
61-
textValue: field,
62-
trailingItems: <TypeBadge tag={tag} />,
63-
};
90+
return 0;
6491
});
92+
93+
return options;
6594
}, [fields, numberTags, stringTags]);
6695

6796
const setSortField = useCallback(

static/app/views/explore/toolbar/toolbarVisualize.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {IconDelete} from 'sentry/icons/iconDelete';
1010
import {t} from 'sentry/locale';
1111
import {defined} from 'sentry/utils';
1212
import type {ParsedFunction} from 'sentry/utils/discover/fields';
13-
import {parseFunction} from 'sentry/utils/discover/fields';
13+
import {parseFunction, prettifyTagKey} from 'sentry/utils/discover/fields';
1414
import {ALLOWED_EXPLORE_VISUALIZE_AGGREGATES} from 'sentry/utils/fields';
1515
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
1616
import type {Visualize} from 'sentry/views/explore/hooks/useVisualizes';
@@ -58,13 +58,30 @@ export function ToolbarVisualize({}: ToolbarVisualizeProps) {
5858
}, [visualizes]);
5959

6060
const fieldOptions: SelectOption<string>[] = useMemo(() => {
61-
const options = Object.values(numberTags).map(tag => {
62-
return {
63-
label: tag.name,
64-
value: tag.key,
65-
textValue: tag.name,
66-
};
67-
});
61+
const unknownOptions = parsedVisualizeGroups
62+
.flatMap(group =>
63+
group.flatMap(entry => {
64+
return entry.func.arguments;
65+
})
66+
)
67+
.filter(option => {
68+
return !numberTags.hasOwnProperty(option);
69+
});
70+
71+
const options = [
72+
...Object.values(numberTags).map(tag => {
73+
return {
74+
label: tag.name,
75+
value: tag.key,
76+
textValue: tag.name,
77+
};
78+
}),
79+
...unknownOptions.map(option => ({
80+
label: prettifyTagKey(option),
81+
value: option,
82+
textValue: option,
83+
})),
84+
];
6885

6986
options.sort((a, b) => {
7087
if (a.label < b.label) {
@@ -79,7 +96,7 @@ export function ToolbarVisualize({}: ToolbarVisualizeProps) {
7996
});
8097

8198
return options;
82-
}, [numberTags]);
99+
}, [numberTags, parsedVisualizeGroups]);
83100

84101
const aggregateOptions: SelectOption<string>[] = useMemo(() => {
85102
return ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.map(aggregate => {

0 commit comments

Comments
 (0)