diff --git a/static/app/components/performance/spanSearchQueryBuilder.tsx b/static/app/components/performance/spanSearchQueryBuilder.tsx index 887c64a9580817..ffe19b36214e72 100644 --- a/static/app/components/performance/spanSearchQueryBuilder.tsx +++ b/static/app/components/performance/spanSearchQueryBuilder.tsx @@ -179,7 +179,12 @@ export function EAPSpanSearchQueryBuilder({ SPANS_FILTER_KEY_SECTIONS.flatMap(section => section.children) ); return [ - ...SPANS_FILTER_KEY_SECTIONS, + ...SPANS_FILTER_KEY_SECTIONS.map(section => { + return { + ...section, + children: section.children.filter(key => stringTags.hasOwnProperty(key)), + }; + }), { value: 'custom_fields', label: 'Custom Tags', diff --git a/static/app/utils/discover/fieldRenderers.tsx b/static/app/utils/discover/fieldRenderers.tsx index 4577ab1d61fe54..876a62dc7bc210 100644 --- a/static/app/utils/discover/fieldRenderers.tsx +++ b/static/app/utils/discover/fieldRenderers.tsx @@ -366,7 +366,6 @@ type SpecialFields = { replayId: SpecialField; 'span.description': SpecialField; 'span.status_code': SpecialField; - span_id: SpecialField; team_key_transaction: SpecialField; 'timestamp.to_day': SpecialField; 'timestamp.to_hour': SpecialField; @@ -482,17 +481,6 @@ const SPECIAL_FIELDS: SpecialFields = { return {getShortEventId(id)}; }, }, - span_id: { - sortField: 'span_id', - renderFunc: data => { - const spanId: string | unknown = data?.span_id; - if (typeof spanId !== 'string') { - return null; - } - - return {getShortEventId(spanId)}; - }, - }, 'span.description': { sortField: 'span.description', renderFunc: data => { diff --git a/static/app/utils/discover/fields.tsx b/static/app/utils/discover/fields.tsx index 7d7968074329b2..72d1d69661bfa4 100644 --- a/static/app/utils/discover/fields.tsx +++ b/static/app/utils/discover/fields.tsx @@ -1639,12 +1639,14 @@ export const COMBINED_DATASET_FILTER_KEY_SECTIONS: FilterKeySection[] = [ // will take in a project platform key, and output only the relevant filter key sections. // This way, users will not be suggested mobile fields for a backend transaction, for example. -export const TYPED_TAG_KEY_RE = /tags\[(.*),(.*)\]/; +export const TYPED_TAG_KEY_RE = /tags\[([^\s]*),([^\s]*)\]/; -export function formatParsedFunction(func: ParsedFunction) { - const args = func.arguments.map(arg => { - const result = arg.match(TYPED_TAG_KEY_RE); - return result?.[1] ?? arg; - }); +export function prettifyTagKey(key: string): string { + const result = key.match(TYPED_TAG_KEY_RE); + return result?.[1] ?? key; +} + +export function prettifyParsedFunction(func: ParsedFunction) { + const args = func.arguments.map(prettifyTagKey); return `${func.name}(${args.join(',')})`; } diff --git a/static/app/views/dashboards/widgetBuilder/widgetBuilder.spec.tsx b/static/app/views/dashboards/widgetBuilder/widgetBuilder.spec.tsx index 717653def0ee5f..d953f67320ad63 100644 --- a/static/app/views/dashboards/widgetBuilder/widgetBuilder.spec.tsx +++ b/static/app/views/dashboards/widgetBuilder/widgetBuilder.spec.tsx @@ -2608,7 +2608,7 @@ describe('WidgetBuilder', function () { body: [ { key: 'plan', - name: 'Plan', + name: 'plan', }, ], match: [ @@ -2621,12 +2621,12 @@ describe('WidgetBuilder', function () { url: `/organizations/org-slug/spans/fields/`, body: [ { - key: 'lcp.size', - name: 'Lcp.Size', + key: 'tags[lcp.size,number]', + name: 'lcp.size', }, { - key: 'something.else', - name: 'Something.Else', + key: 'tags[something.else,number]', + name: 'something.else', }, ], match: [ diff --git a/static/app/views/explore/charts/index.tsx b/static/app/views/explore/charts/index.tsx index 6030c6a9cf92dc..2cfcd3a90a71a0 100644 --- a/static/app/views/explore/charts/index.tsx +++ b/static/app/views/explore/charts/index.tsx @@ -14,8 +14,8 @@ import {space} from 'sentry/styles/space'; import {dedupeArray} from 'sentry/utils/dedupeArray'; import { aggregateOutputType, - formatParsedFunction, parseFunction, + prettifyParsedFunction, } from 'sentry/utils/discover/fields'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; @@ -172,7 +172,7 @@ export function ExploreCharts({query, setError}: ExploreChartsProps) { const formattedYAxes = dedupedYAxes.map(yaxis => { const func = parseFunction(yaxis); - return func ? formatParsedFunction(func) : undefined; + return func ? prettifyParsedFunction(func) : undefined; }); const {chartType, label, yAxes: visualizeYAxes} = visualize; diff --git a/static/app/views/explore/contexts/spanTagsContext.tsx b/static/app/views/explore/contexts/spanTagsContext.tsx index 29ae6185475cf1..85459a48055983 100644 --- a/static/app/views/explore/contexts/spanTagsContext.tsx +++ b/static/app/views/explore/contexts/spanTagsContext.tsx @@ -9,10 +9,7 @@ import {useApiQuery} from 'sentry/utils/queryClient'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {SpanIndexedField} from 'sentry/views/insights/types'; -import { - useSpanFieldStaticTags, - useSpanFieldSupportedTags, -} from 'sentry/views/performance/utils/useSpanFieldSupportedTags'; +import {useSpanFieldCustomTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags'; type TypedSpanTags = { number: TagCollection; @@ -27,22 +24,9 @@ interface SpanTagsProviderProps { } export function SpanTagsProvider({children, dataset}: SpanTagsProviderProps) { - const numericSpanFields: Set = useMemo(() => { - return new Set([ - SpanIndexedField.SPAN_DURATION, - SpanIndexedField.SPAN_SELF_TIME, - SpanIndexedField.INP, - SpanIndexedField.INP_SCORE, - SpanIndexedField.INP_SCORE_WEIGHT, - SpanIndexedField.TOTAL_SCORE, - SpanIndexedField.CACHE_ITEM_SIZE, - SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE, - SpanIndexedField.MESSAGING_MESSAGE_RECEIVE_LATENCY, - SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT, - ]); - }, []); - - const supportedTags = useSpanFieldSupportedTags(); + const {data: indexedTags} = useSpanFieldCustomTags({ + enabled: dataset === DiscoverDatasets.SPANS_INDEXED, + }); const numberTags: TagCollection = useTypedSpanTags({ enabled: dataset === DiscoverDatasets.SPANS_EAP, @@ -54,42 +38,92 @@ export function SpanTagsProvider({children, dataset}: SpanTagsProviderProps) { type: 'string', }); - const staticTags = useSpanFieldStaticTags(); - const allNumberTags = useMemo(() => { + const measurements = [ + SpanIndexedField.SPAN_DURATION, + SpanIndexedField.SPAN_SELF_TIME, + ].map(measurement => [ + measurement, + { + key: measurement, + name: measurement, + kind: FieldKind.MEASUREMENT, + }, + ]); + if (dataset === DiscoverDatasets.SPANS_INDEXED) { - return {}; + return { + ...Object.fromEntries(measurements), + }; } return { ...numberTags, - ...Object.fromEntries( - Object.entries(staticTags) - .filter(([key, _]) => numericSpanFields.has(key)) - .map(([key, tag]) => [key, {...tag, kind: FieldKind.MEASUREMENT}]) - ), + ...Object.fromEntries(measurements), }; - }, [dataset, numberTags, numericSpanFields, staticTags]); + }, [dataset, numberTags]); const allStringTags = useMemo(() => { + const tags = [ + // NOTE: intentionally choose to not expose transaction id + // as we're moving toward span ids + + 'id', // SpanIndexedField.SPAN_OP is actually `span_id` + SpanIndexedField.BROWSER_NAME, + SpanIndexedField.ENVIRONMENT, + SpanIndexedField.ORIGIN_TRANSACTION, + SpanIndexedField.PROJECT, + SpanIndexedField.RAW_DOMAIN, + SpanIndexedField.RELEASE, + SpanIndexedField.SDK_NAME, + SpanIndexedField.SDK_VERSION, + SpanIndexedField.SPAN_ACTION, + SpanIndexedField.SPAN_CATEGORY, + SpanIndexedField.SPAN_DESCRIPTION, + SpanIndexedField.SPAN_DOMAIN, + SpanIndexedField.SPAN_GROUP, + SpanIndexedField.SPAN_MODULE, + SpanIndexedField.SPAN_OP, + SpanIndexedField.SPAN_STATUS, + SpanIndexedField.TIMESTAMP, + SpanIndexedField.TRACE, + SpanIndexedField.TRANSACTION, + SpanIndexedField.TRANSACTION_METHOD, + SpanIndexedField.TRANSACTION_OP, + SpanIndexedField.USER, + SpanIndexedField.USER_EMAIL, + SpanIndexedField.USER_GEO_SUBREGION, + SpanIndexedField.USER_ID, + SpanIndexedField.USER_IP, + SpanIndexedField.USER_USERNAME, + ].map(tag => [ + tag, + { + key: tag, + name: tag, + kind: FieldKind.TAG, + }, + ]); + if (dataset === DiscoverDatasets.SPANS_INDEXED) { - return supportedTags.data; + return { + ...indexedTags, + ...Object.fromEntries(tags), + }; } return { ...stringTags, - ...Object.fromEntries( - Object.entries(staticTags) - .filter(([key, _]) => !numericSpanFields.has(key)) - .map(([key, tag]) => [key, {...tag, kind: FieldKind.TAG}]) - ), + ...Object.fromEntries(tags), }; - }, [dataset, supportedTags, stringTags, staticTags, numericSpanFields]); + }, [dataset, indexedTags, stringTags]); - const tags = { - number: allNumberTags, - string: allStringTags, - }; + const tags = useMemo(() => { + return { + number: allNumberTags, + string: allStringTags, + }; + }, [allNumberTags, allStringTags]); return {children}; } @@ -131,6 +165,7 @@ function useTypedSpanTags({ environment: selection.environments, ...normalizeDateTimeParams(selection.datetime), dataset: 'spans', + process: 1, type, }, }; @@ -149,20 +184,22 @@ function useTypedSpanTags({ // For now, skip all the sentry. prefixed tags as they // should be covered by the static tags that will be // merged with these results. - if (tag.key.startsWith('sentry.')) { + if (tag.key.startsWith('sentry.') || tag.key.startsWith('tags[sentry.')) { continue; } // EAP spans contain tags with illegal characters - if (!/^[a-zA-Z0-9_.:-]+$/.test(tag.key)) { + // SnQL forbids `-` but is allowed in RPC. So add it back later + if ( + !/^[a-zA-Z0-9_.:]+$/.test(tag.key) && + !/^tags\[[a-zA-Z0-9_.:]+,number\]$/.test(tag.key) + ) { continue; } - const key = type === 'number' ? `tags[${tag.key},number]` : tag.key; - - allTags[key] = { - key, - name: tag.key, + allTags[tag.key] = { + key: tag.key, + name: tag.name, kind: type === 'number' ? FieldKind.MEASUREMENT : FieldKind.TAG, }; } diff --git a/static/app/views/explore/hooks/useResultsMode.spec.tsx b/static/app/views/explore/hooks/useResultsMode.spec.tsx index ca060618933591..92b8a7594c98ec 100644 --- a/static/app/views/explore/hooks/useResultsMode.spec.tsx +++ b/static/app/views/explore/hooks/useResultsMode.spec.tsx @@ -39,7 +39,7 @@ describe('useResultMode', function () { expect(resultMode).toEqual('samples'); // default expect(sampleFields).toEqual([ - 'span_id', + 'id', 'project', 'span.op', 'span.description', @@ -56,7 +56,7 @@ describe('useResultMode', function () { expect(resultMode).toEqual('samples'); expect(sampleFields).toEqual([ - 'span_id', + 'id', 'project', 'span.op', 'span.description', diff --git a/static/app/views/explore/hooks/useSampleFields.spec.tsx b/static/app/views/explore/hooks/useSampleFields.spec.tsx index 2e5f464458e05c..59ccc2aff095b4 100644 --- a/static/app/views/explore/hooks/useSampleFields.spec.tsx +++ b/static/app/views/explore/hooks/useSampleFields.spec.tsx @@ -14,7 +14,7 @@ describe('useSampleFields', function () { render(, {disableRouterMocks: true}); expect(sampleFields).toEqual([ - 'span_id', + 'id', 'project', 'span.op', 'span.description', @@ -27,7 +27,7 @@ describe('useSampleFields', function () { act(() => setSampleFields([])); expect(sampleFields).toEqual([ - 'span_id', + 'id', 'project', 'span.op', 'span.description', diff --git a/static/app/views/explore/hooks/useSampleFields.tsx b/static/app/views/explore/hooks/useSampleFields.tsx index 762425d8773c44..701e6b80db9458 100644 --- a/static/app/views/explore/hooks/useSampleFields.tsx +++ b/static/app/views/explore/hooks/useSampleFields.tsx @@ -31,14 +31,7 @@ function useSampleFieldsImpl({ return fields; } - return [ - 'span_id', - 'project', - 'span.op', - 'span.description', - 'span.duration', - 'timestamp', - ]; + return ['id', 'project', 'span.op', 'span.description', 'span.duration', 'timestamp']; }, [location.query.field]); const setSampleFields = useCallback( diff --git a/static/app/views/explore/tables/aggregatesTable.tsx b/static/app/views/explore/tables/aggregatesTable.tsx index be597c601a42c0..cc1e9ffe411508 100644 --- a/static/app/views/explore/tables/aggregatesTable.tsx +++ b/static/app/views/explore/tables/aggregatesTable.tsx @@ -15,9 +15,9 @@ import EventView from 'sentry/utils/discover/eventView'; import type {Sort} from 'sentry/utils/discover/fields'; import { fieldAlignment, - formatParsedFunction, getAggregateAlias, parseFunction, + prettifyParsedFunction, } from 'sentry/utils/discover/fields'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; @@ -147,7 +147,7 @@ export function AggregatesTable({setError}: AggregatesTableProps) { const func = parseFunction(field); if (func) { - label = formatParsedFunction(func); + label = prettifyParsedFunction(func); } const direction = sorts.find(s => s.field === field)?.kind; diff --git a/static/app/views/explore/tables/fieldRenderer.tsx b/static/app/views/explore/tables/fieldRenderer.tsx index 49419bad455e3c..5ca833eb4a7520 100644 --- a/static/app/views/explore/tables/fieldRenderer.tsx +++ b/static/app/views/explore/tables/fieldRenderer.tsx @@ -6,7 +6,9 @@ import type {TableDataRow} from 'sentry/utils/discover/discoverQuery'; import type {EventData, MetaType} from 'sentry/utils/discover/eventView'; import EventView from 'sentry/utils/discover/eventView'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; +import {Container} from 'sentry/utils/discover/styles'; import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; +import {getShortEventId} from 'sentry/utils/events'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; @@ -33,7 +35,7 @@ export function FieldRenderer({data, meta, unit, column}: FieldProps) { const query = new MutableSearch(userQuery); const field = column.name; - const renderer = getFieldRenderer(field, meta, false); + const renderer = getExploreFieldRenderer(field, meta); let rendered = renderer(data, { location, @@ -91,6 +93,28 @@ export function FieldRenderer({data, meta, unit, column}: FieldProps) { ); } +function getExploreFieldRenderer( + field: string, + meta: MetaType +): ReturnType { + if (field === 'id' || field === 'span_id') { + return eventIdRenderFunc(field); + } + return getFieldRenderer(field, meta, false); +} + +function eventIdRenderFunc(field: string) { + function renderer(data: EventData) { + const spanId: string | unknown = data?.[field]; + if (typeof spanId !== 'string') { + return null; + } + + return {getShortEventId(spanId)}; + } + return renderer; +} + const StyledTimeSince = styled(TimeSince)` width: fit-content; `; diff --git a/static/app/views/explore/tables/spansTable.tsx b/static/app/views/explore/tables/spansTable.tsx index a43b5247573942..cbf4274f3902e1 100644 --- a/static/app/views/explore/tables/spansTable.tsx +++ b/static/app/views/explore/tables/spansTable.tsx @@ -56,7 +56,7 @@ export function SpansTable({setError}: SpansTableProps) { 'project', 'trace', 'transaction.span_id', - 'span_id', + 'id', 'timestamp', ]; diff --git a/static/app/views/explore/toolbar/index.spec.tsx b/static/app/views/explore/toolbar/index.spec.tsx index cc06217d93f02f..2239519d172c7b 100644 --- a/static/app/views/explore/toolbar/index.spec.tsx +++ b/static/app/views/explore/toolbar/index.spec.tsx @@ -53,7 +53,7 @@ describe('ExploreToolbar', function () { expect(resultMode).toEqual('samples'); expect(sampleFields).toEqual([ - 'span_id', + 'id', 'project', 'span.op', 'span.description', @@ -80,7 +80,7 @@ describe('ExploreToolbar', function () { expect(resultMode).toEqual('samples'); expect(sampleFields).toEqual([ - 'span_id', + 'id', 'project', 'span.op', 'span.description', @@ -217,7 +217,7 @@ describe('ExploreToolbar', function () { // check the default field options const fields = [ - 'span_id', + 'id', 'project', 'span.op', 'span.description', diff --git a/static/app/views/explore/toolbar/toolbarSortBy.tsx b/static/app/views/explore/toolbar/toolbarSortBy.tsx index e1906a45507e9b..af7a32ce161ad0 100644 --- a/static/app/views/explore/toolbar/toolbarSortBy.tsx +++ b/static/app/views/explore/toolbar/toolbarSortBy.tsx @@ -6,7 +6,7 @@ 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 {formatParsedFunction, parseFunction} from 'sentry/utils/discover/fields'; +import {parseFunction, prettifyParsedFunction} from 'sentry/utils/discover/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'; @@ -45,7 +45,7 @@ export function ToolbarSortBy({fields, setSorts, sorts}: ToolbarSortByProps) { const func = parseFunction(field); if (func) { - const formatted = formatParsedFunction(func); + const formatted = prettifyParsedFunction(func); return { label: formatted, value: field, diff --git a/static/app/views/insights/constants.tsx b/static/app/views/insights/constants.tsx index 3d4c5cf5b44f36..47c46bcf6cca9e 100644 --- a/static/app/views/insights/constants.tsx +++ b/static/app/views/insights/constants.tsx @@ -87,6 +87,7 @@ const USER_CONTEXT_FILTERS: FilterKeySection = { children: [ SpanIndexedField.USER, SpanIndexedField.USER_ID, + SpanIndexedField.USER_IP, SpanIndexedField.USER_EMAIL, SpanIndexedField.USER_USERNAME, SpanIndexedField.USER_GEO_SUBREGION, diff --git a/static/app/views/insights/types.tsx b/static/app/views/insights/types.tsx index 81f36d2321e158..af4efcb98be3eb 100644 --- a/static/app/views/insights/types.tsx +++ b/static/app/views/insights/types.tsx @@ -229,6 +229,7 @@ export enum SpanIndexedField { SPAN_AI_PIPELINE_GROUP = 'span.ai.pipeline.group', SPAN_AI_PIPELINE_GROUP_TAG = 'ai_pipeline_group', SDK_NAME = 'sdk.name', + SDK_VERSION = 'sdk.version', TRACE = 'trace', TRANSACTION_ID = 'transaction.id', TRANSACTION_METHOD = 'transaction.method', @@ -246,6 +247,7 @@ export enum SpanIndexedField { BROWSER_NAME = 'browser.name', USER = 'user', USER_ID = 'user.id', + USER_IP = 'user.ip', USER_EMAIL = 'user.email', USER_USERNAME = 'user.username', INP = 'measurements.inp', @@ -268,6 +270,7 @@ export type SpanIndexedResponse = { [SpanIndexedField.ENVIRONMENT]: string; [SpanIndexedField.RELEASE]: string; [SpanIndexedField.SDK_NAME]: string; + [SpanIndexedField.SDK_VERSION]: string; [SpanIndexedField.SPAN_CATEGORY]: string; [SpanIndexedField.SPAN_DURATION]: number; [SpanIndexedField.SPAN_SELF_TIME]: number; diff --git a/static/app/views/performance/utils/useSpanFieldSupportedTags.tsx b/static/app/views/performance/utils/useSpanFieldSupportedTags.tsx index 7023051ad694b8..26ece7815db1e4 100644 --- a/static/app/views/performance/utils/useSpanFieldSupportedTags.tsx +++ b/static/app/views/performance/utils/useSpanFieldSupportedTags.tsx @@ -66,8 +66,11 @@ export function useSpanMetricsFieldSupportedTags(options?: {excludedTags?: strin ); } -export function useSpanFieldCustomTags(options?: {projects?: PageFilters['projects']}) { - const {projects} = options || {}; +export function useSpanFieldCustomTags(options?: { + enabled?: boolean; + projects?: PageFilters['projects']; +}) { + const {enabled, projects} = options || {}; const {selection} = usePageFilters(); const organization = useOrganization(); @@ -80,6 +83,7 @@ export function useSpanFieldCustomTags(options?: {projects?: PageFilters['projec { staleTime: 0, retry: false, + enabled, } );