From e222d0694846366cb88934e49f536e13c5fa08ef Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Mon, 25 Nov 2024 13:10:21 -0500 Subject: [PATCH 1/2] feat(explore): Format numeric tags nicely in explore Some tags may not have loaded or is from a different project. In thise case, we should try to render them nicely. --- .../views/explore/components/typeBadge.tsx | 11 +-- .../explore/tables/columnEditorModal.spec.tsx | 20 ++++- .../explore/tables/columnEditorModal.tsx | 4 +- .../app/views/explore/tables/spansTable.tsx | 4 +- .../app/views/explore/toolbar/index.spec.tsx | 2 +- .../views/explore/toolbar/toolbarGroupBy.tsx | 40 ++++------ .../views/explore/toolbar/toolbarSortBy.tsx | 75 +++++++++++++------ .../explore/toolbar/toolbarVisualize.tsx | 35 ++++++--- 8 files changed, 121 insertions(+), 70 deletions(-) diff --git a/static/app/views/explore/components/typeBadge.tsx b/static/app/views/explore/components/typeBadge.tsx index ca37c76f104f12..2e247ac99d8527 100644 --- a/static/app/views/explore/components/typeBadge.tsx +++ b/static/app/views/explore/components/typeBadge.tsx @@ -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 {t('aggregation')}; } - if (tag?.kind === FieldKind.MEASUREMENT) { + if (kind === FieldKind.MEASUREMENT) { return {t('number')}; } - if (tag?.kind === FieldKind.TAG) { + + if (kind === FieldKind.TAG) { return {t('string')}; } + return null; } diff --git a/static/app/views/explore/tables/columnEditorModal.spec.tsx b/static/app/views/explore/tables/columnEditorModal.spec.tsx index 214efd0a7b2ec4..6593e463c4c1a3 100644 --- a/static/app/views/explore/tables/columnEditorModal.spec.tsx +++ b/static/app/views/explore/tables/columnEditorModal.spec.tsx @@ -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]); @@ -170,11 +176,17 @@ describe('ColumnEditorModal', function () { expect(column).toHaveTextContent(columns1[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 project'})); 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]); diff --git a/static/app/views/explore/tables/columnEditorModal.tsx b/static/app/views/explore/tables/columnEditorModal.tsx index 900216a33651c5..6bcb7bee0cb186 100644 --- a/static/app/views/explore/tables/columnEditorModal.tsx +++ b/static/app/views/explore/tables/columnEditorModal.tsx @@ -45,7 +45,7 @@ export function ColumnEditorModal({ label: tag.name, value: tag.key, textValue: tag.name, - trailingItems: , + trailingItems: , }; }), ...Object.values(numberTags).map(tag => { @@ -53,7 +53,7 @@ export function ColumnEditorModal({ label: tag.name, value: tag.key, textValue: tag.name, - trailingItems: , + trailingItems: , }; }), ]; diff --git a/static/app/views/explore/tables/spansTable.tsx b/static/app/views/explore/tables/spansTable.tsx index 547ab5f6218e5a..ca502a0ca07f51 100644 --- a/static/app/views/explore/tables/spansTable.tsx +++ b/static/app/views/explore/tables/spansTable.tsx @@ -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'; @@ -151,7 +151,7 @@ export function SpansTable({setError}: SpansTableProps) { isFirst={i === 0} onClick={updateSort} > - {tag?.name ?? field} + {tag?.name ?? prettifyTagKey(field)} {defined(direction) && ( [] = 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]); diff --git a/static/app/views/explore/toolbar/toolbarSortBy.tsx b/static/app/views/explore/toolbar/toolbarSortBy.tsx index af7a32ce161ad0..3ce7343f36f794 100644 --- a/static/app/views/explore/toolbar/toolbarSortBy.tsx +++ b/static/app/views/explore/toolbar/toolbarSortBy.tsx @@ -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'; @@ -32,36 +38,59 @@ export function ToolbarSortBy({fields, setSorts, sorts}: ToolbarSortByProps) { const stringTags = useSpanTags('string'); const fieldOptions: SelectOption[] = 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: , + }; + } + + const func = parseFunction(field); + if (func) { + const formatted = prettifyParsedFunction(func); + return { + label: formatted, + value: field, + textValue: formatted, + trailingItems: , + }; + } + + 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: , + textValue: field, + trailingItems: , }; + }), + ]; + + 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: , - }; + if (a.label > b.label) { + return 1; } - // not a tag, maybe it's an aggregate - return { - label: field, - value: field, - textValue: field, - trailingItems: , - }; + return 0; }); + + return options; }, [fields, numberTags, stringTags]); const setSortField = useCallback( diff --git a/static/app/views/explore/toolbar/toolbarVisualize.tsx b/static/app/views/explore/toolbar/toolbarVisualize.tsx index f204cf59042e96..ccd98bc1321103 100644 --- a/static/app/views/explore/toolbar/toolbarVisualize.tsx +++ b/static/app/views/explore/toolbar/toolbarVisualize.tsx @@ -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'; @@ -58,13 +58,30 @@ export function ToolbarVisualize({}: ToolbarVisualizeProps) { }, [visualizes]); const fieldOptions: SelectOption[] = 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) { @@ -79,7 +96,7 @@ export function ToolbarVisualize({}: ToolbarVisualizeProps) { }); return options; - }, [numberTags]); + }, [numberTags, parsedVisualizeGroups]); const aggregateOptions: SelectOption[] = useMemo(() => { return ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.map(aggregate => { From 5dad5108b007a71ab37d6936c66d531c051aabd2 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Mon, 25 Nov 2024 14:09:51 -0500 Subject: [PATCH 2/2] fix tests --- static/app/views/explore/tables/columnEditorModal.spec.tsx | 2 +- static/app/views/explore/tables/columnEditorModal.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/static/app/views/explore/tables/columnEditorModal.spec.tsx b/static/app/views/explore/tables/columnEditorModal.spec.tsx index 6593e463c4c1a3..42fdead7c28858 100644 --- a/static/app/views/explore/tables/columnEditorModal.spec.tsx +++ b/static/app/views/explore/tables/columnEditorModal.spec.tsx @@ -182,7 +182,7 @@ describe('ColumnEditorModal', function () { ['span.duration', 'number'], ['span.op', 'string'], ]; - await userEvent.click(screen.getByRole('button', {name: 'Column project'})); + 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][0]); diff --git a/static/app/views/explore/tables/columnEditorModal.tsx b/static/app/views/explore/tables/columnEditorModal.tsx index 6bcb7bee0cb186..9c8651e6fd338f 100644 --- a/static/app/views/explore/tables/columnEditorModal.tsx +++ b/static/app/views/explore/tables/columnEditorModal.tsx @@ -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'; @@ -45,7 +46,7 @@ export function ColumnEditorModal({ label: tag.name, value: tag.key, textValue: tag.name, - trailingItems: , + trailingItems: , }; }), ...Object.values(numberTags).map(tag => { @@ -53,7 +54,7 @@ export function ColumnEditorModal({ label: tag.name, value: tag.key, textValue: tag.name, - trailingItems: , + trailingItems: , }; }), ];