Skip to content

feat(explore): Format numeric tags nicely in explore #81255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions static/app/views/explore/components/typeBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import BadgeTag from 'sentry/components/badge/tag';
import {t} from 'sentry/locale';
import type {Tag} from 'sentry/types/group';
import {defined} from 'sentry/utils';
import type {ParsedFunction} from 'sentry/utils/discover/fields';
import {FieldKind} from 'sentry/utils/fields';

interface TypeBadgeProps {
func?: ParsedFunction;
tag?: Tag;
kind?: FieldKind;
}

export function TypeBadge({func, tag}: TypeBadgeProps) {
export function TypeBadge({func, kind}: TypeBadgeProps) {
if (defined(func)) {
return <BadgeTag type="warning">{t('aggregation')}</BadgeTag>;
}

if (tag?.kind === FieldKind.MEASUREMENT) {
if (kind === FieldKind.MEASUREMENT) {
return <BadgeTag type="success">{t('number')}</BadgeTag>;
}
if (tag?.kind === FieldKind.TAG) {

if (kind === FieldKind.TAG) {
return <BadgeTag type="highlight">{t('string')}</BadgeTag>;
}

return null;
}
22 changes: 17 additions & 5 deletions static/app/views/explore/tables/columnEditorModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,17 @@ describe('ColumnEditorModal', function () {
expect(column).toHaveTextContent(columns2[i]);
});

const options = ['id', 'project', 'span.duration', 'span.op'];
const options: [string, 'string' | 'number'][] = [
['id', 'string'],
['project', 'string'],
['span.duration', 'number'],
['span.op', 'string'],
];
await userEvent.click(screen.getByRole('button', {name: 'Column None'}));
const columnOptions = await screen.findAllByRole('option');
columnOptions.forEach((option, i) => {
expect(option).toHaveTextContent(options[i]);
expect(option).toHaveTextContent(options[i][0]);
expect(option).toHaveTextContent(options[i][1]);
});

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

const options = ['id', 'project', 'span.duration', 'span.op'];
await userEvent.click(screen.getByRole('button', {name: 'Column project'}));
const options: [string, 'string' | 'number'][] = [
['id', 'string'],
['project', 'string'],
['span.duration', 'number'],
['span.op', 'string'],
];
await userEvent.click(screen.getByRole('button', {name: 'Column project string'}));
const columnOptions = await screen.findAllByRole('option');
columnOptions.forEach((option, i) => {
expect(option).toHaveTextContent(options[i]);
expect(option).toHaveTextContent(options[i][0]);
expect(option).toHaveTextContent(options[i][1]);
});

await userEvent.click(columnOptions[3]);
Expand Down
5 changes: 3 additions & 2 deletions static/app/views/explore/tables/columnEditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {TagCollection} from 'sentry/types/group';
import {defined} from 'sentry/utils';
import {FieldKind} from 'sentry/utils/fields';
import {TypeBadge} from 'sentry/views/explore/components/typeBadge';

import {DragNDropContext} from '../contexts/dragNDropContext';
Expand Down Expand Up @@ -45,15 +46,15 @@ export function ColumnEditorModal({
label: tag.name,
value: tag.key,
textValue: tag.name,
trailingItems: <TypeBadge tag={tag} />,
trailingItems: <TypeBadge kind={FieldKind.TAG} />,
};
}),
...Object.values(numberTags).map(tag => {
return {
label: tag.name,
value: tag.key,
textValue: tag.name,
trailingItems: <TypeBadge tag={tag} />,
trailingItems: <TypeBadge kind={FieldKind.MEASUREMENT} />,
};
}),
];
Expand Down
4 changes: 2 additions & 2 deletions static/app/views/explore/tables/spansTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {t} from 'sentry/locale';
import type {NewQuery} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import EventView from 'sentry/utils/discover/eventView';
import {fieldAlignment} from 'sentry/utils/discover/fields';
import {fieldAlignment, prettifyTagKey} from 'sentry/utils/discover/fields';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
Expand Down Expand Up @@ -151,7 +151,7 @@ export function SpansTable({setError}: SpansTableProps) {
isFirst={i === 0}
onClick={updateSort}
>
<span>{tag?.name ?? field}</span>
<span>{tag?.name ?? prettifyTagKey(field)}</span>
{defined(direction) && (
<IconArrow
size="xs"
Expand Down
2 changes: 1 addition & 1 deletion static/app/views/explore/toolbar/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ describe('ExploreToolbar', function () {
const fields = [
'id',
'project',
'span.op',
'span.description',
'span.duration',
'span.op',
'timestamp',
];
await userEvent.click(within(section).getByRole('button', {name: 'timestamp'}));
Expand Down
40 changes: 16 additions & 24 deletions static/app/views/explore/toolbar/toolbarGroupBy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,34 +39,26 @@ export function ToolbarGroupBy({disabled}: ToolbarGroupByProps) {
const {groupBys, setGroupBys} = useGroupBys();

const options: SelectOption<string>[] = useMemo(() => {
// These options aren't known to exist on this project but it was inserted into
// the group bys somehow so it should be a valid options in the group bys.
//
// One place this may come from is when switching projects/environment/date range,
// a tag may disappear based on the selection.
const unknownOptions = groupBys
.filter(groupBy => groupBy && !tags.hasOwnProperty(groupBy))
.map(groupBy => {
return {
label: groupBy,
value: groupBy,
textValue: groupBy,
};
});

const knownOptions = Object.keys(tags).map(tagKey => {
return {
label: tagKey,
value: tagKey,
textValue: tagKey,
};
});
const potentialOptions = [
...Object.keys(tags),

// These options aren't known to exist on this project but it was inserted into
// the group bys somehow so it should be a valid options in the group bys.
//
// One place this may come from is when switching projects/environment/date range,
// a tag may disappear based on the selection.
...groupBys.filter(groupBy => groupBy && !tags.hasOwnProperty(groupBy)),
];
potentialOptions.sort();

return [
// hard code in an empty option
{label: t('None'), value: '', textValue: t('none')},
...unknownOptions,
...knownOptions,
...potentialOptions.map(key => ({
label: key,
value: key,
textValue: key,
})),
];
}, [groupBys, tags]);

Expand Down
75 changes: 52 additions & 23 deletions static/app/views/explore/toolbar/toolbarSortBy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {CompactSelect} from 'sentry/components/compactSelect';
import {Tooltip} from 'sentry/components/tooltip';
import {t} from 'sentry/locale';
import type {Sort} from 'sentry/utils/discover/fields';
import {parseFunction, prettifyParsedFunction} from 'sentry/utils/discover/fields';
import {
parseFunction,
prettifyParsedFunction,
prettifyTagKey,
TYPED_TAG_KEY_RE,
} from 'sentry/utils/discover/fields';
import {FieldKind} from 'sentry/utils/fields';
import {TypeBadge} from 'sentry/views/explore/components/typeBadge';
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
import {useResultMode} from 'sentry/views/explore/hooks/useResultsMode';
Expand All @@ -32,36 +38,59 @@ export function ToolbarSortBy({fields, setSorts, sorts}: ToolbarSortByProps) {
const stringTags = useSpanTags('string');

const fieldOptions: SelectOption<Field>[] = useMemo(() => {
return fields.map(field => {
const tag = stringTags[field] ?? numberTags[field] ?? null;
if (tag) {
const options = [
...new Set(fields).keys().map(field => {
const tag = stringTags[field] ?? numberTags[field] ?? null;
if (tag) {
return {
label: tag.name,
value: field,
textValue: tag.name,
trailingItems: <TypeBadge kind={tag?.kind} />,
};
}

const func = parseFunction(field);
if (func) {
const formatted = prettifyParsedFunction(func);
return {
label: formatted,
value: field,
textValue: formatted,
trailingItems: <TypeBadge func={func} />,
};
}

const result = field.match(TYPED_TAG_KEY_RE);
const kind =
result?.[2] === 'string'
? FieldKind.TAG
: result?.[2] === 'number'
? FieldKind.MEASUREMENT
: undefined;

return {
label: tag.name,
label: prettifyTagKey(field),
value: field,
textValue: tag.name,
trailingItems: <TypeBadge tag={tag} />,
textValue: field,
trailingItems: <TypeBadge kind={kind} />,
};
}),
];

options.sort((a, b) => {
if (a.label < b.label) {
return -1;
}

const func = parseFunction(field);
if (func) {
const formatted = prettifyParsedFunction(func);
return {
label: formatted,
value: field,
textValue: formatted,
trailingItems: <TypeBadge func={func} />,
};
if (a.label > b.label) {
return 1;
}

// not a tag, maybe it's an aggregate
return {
label: field,
value: field,
textValue: field,
trailingItems: <TypeBadge tag={tag} />,
};
return 0;
});

return options;
}, [fields, numberTags, stringTags]);

const setSortField = useCallback(
Expand Down
35 changes: 26 additions & 9 deletions static/app/views/explore/toolbar/toolbarVisualize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {IconDelete} from 'sentry/icons/iconDelete';
import {t} from 'sentry/locale';
import {defined} from 'sentry/utils';
import type {ParsedFunction} from 'sentry/utils/discover/fields';
import {parseFunction} from 'sentry/utils/discover/fields';
import {parseFunction, prettifyTagKey} from 'sentry/utils/discover/fields';
import {ALLOWED_EXPLORE_VISUALIZE_AGGREGATES} from 'sentry/utils/fields';
import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
import type {Visualize} from 'sentry/views/explore/hooks/useVisualizes';
Expand Down Expand Up @@ -58,13 +58,30 @@ export function ToolbarVisualize({}: ToolbarVisualizeProps) {
}, [visualizes]);

const fieldOptions: SelectOption<string>[] = useMemo(() => {
const options = Object.values(numberTags).map(tag => {
return {
label: tag.name,
value: tag.key,
textValue: tag.name,
};
});
const unknownOptions = parsedVisualizeGroups
.flatMap(group =>
group.flatMap(entry => {
return entry.func.arguments;
})
)
.filter(option => {
return !numberTags.hasOwnProperty(option);
});

const options = [
...Object.values(numberTags).map(tag => {
return {
label: tag.name,
value: tag.key,
textValue: tag.name,
};
}),
...unknownOptions.map(option => ({
label: prettifyTagKey(option),
value: option,
textValue: option,
})),
];

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

return options;
}, [numberTags]);
}, [numberTags, parsedVisualizeGroups]);

const aggregateOptions: SelectOption<string>[] = useMemo(() => {
return ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.map(aggregate => {
Expand Down
Loading