import type {ReactNode} from 'react'; import type {Location} from 'history'; import type {GridColumnOrder} from 'sentry/components/gridEditable'; import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable'; import SortLink from 'sentry/components/gridEditable/sortLink'; import Link from 'sentry/components/links/link'; import {t} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {defined} from 'sentry/utils'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; import type {ColumnType} from 'sentry/utils/discover/fields'; import {fieldAlignment} from 'sentry/utils/discover/fields'; import {Container as TableCellContainer} from 'sentry/utils/discover/styles'; import type {SuspectSpans} from 'sentry/utils/performance/suspectSpans/types'; import {spanDetailsRouteWithQuery} from './spanDetails/utils'; import type {SpanSort, SpansTotalValues} from './types'; import {SpanSortOthers, SpanSortPercentiles} from './types'; type Props = { isLoading: boolean; location: Location; organization: Organization; sort: SpanSort; suspectSpans: SuspectSpans; totals: SpansTotalValues | null; transactionName: string; project?: Project; }; export default function SuspectSpansTable(props: Props) { const { location, organization, transactionName, isLoading, suspectSpans, totals, sort, project, } = props; const data: TableDataRowWithExtras[] = suspectSpans.map(suspectSpan => ({ operation: suspectSpan.op, group: suspectSpan.group, description: suspectSpan.description, frequency: // Frequency is computed using the `uniq` function in ClickHouse. // Because it is an approximation, it can occasionally exceed the number of events. defined(suspectSpan.frequency) && defined(totals?.['count()']) ? Math.min(1, suspectSpan.frequency / totals!['count()']) : null, avgOccurrences: suspectSpan.avgOccurrences, p50ExclusiveTime: suspectSpan.p50ExclusiveTime, p75ExclusiveTime: suspectSpan.p75ExclusiveTime, p95ExclusiveTime: suspectSpan.p95ExclusiveTime, p99ExclusiveTime: suspectSpan.p99ExclusiveTime, sumExclusiveTime: suspectSpan.sumExclusiveTime, })); return ( COLUMNS[column])} columnSortBy={[]} grid={{ renderHeadCell, renderBodyCell: renderBodyCellWithMeta( location, organization, transactionName, project ), }} /> ); } function renderHeadCell(column: TableColumn, _index: number): ReactNode { const align = fieldAlignment(column.key, COLUMN_TYPE[column.key]); return ( undefined} /> ); } function renderBodyCellWithMeta( location: Location, organization: Organization, transactionName: string, project?: Project ) { return function ( column: TableColumn, dataRow: TableDataRowWithExtras ): React.ReactNode { const fieldRenderer = getFieldRenderer(column.key, COLUMN_TYPE); if (column.key === 'description') { const target = spanDetailsRouteWithQuery({ orgSlug: organization.slug, transaction: transactionName, query: location.query, spanSlug: {op: dataRow.operation, group: dataRow.group}, projectID: project?.id, }); return ( {dataRow[column.key] ?? t('(unnamed span)')} ); } return fieldRenderer(dataRow, {location, organization}); }; } type TableColumnKey = | 'operation' | 'description' | 'frequency' | 'avgOccurrences' | 'p50ExclusiveTime' | 'p75ExclusiveTime' | 'p95ExclusiveTime' | 'p99ExclusiveTime' | 'sumExclusiveTime'; type TableColumn = GridColumnOrder; type TableDataRow = Record; type TableDataRowWithExtras = TableDataRow & { group: string; }; const COLUMN_ORDER: Record = { [SpanSortOthers.AVG_OCCURRENCE]: [ 'operation', 'description', 'avgOccurrences', 'frequency', 'p75ExclusiveTime', 'sumExclusiveTime', ], [SpanSortOthers.SUM_EXCLUSIVE_TIME]: [ 'operation', 'description', 'frequency', 'p75ExclusiveTime', 'sumExclusiveTime', ], [SpanSortPercentiles.P50_EXCLUSIVE_TIME]: [ 'operation', 'description', 'frequency', 'p50ExclusiveTime', 'sumExclusiveTime', ], [SpanSortPercentiles.P75_EXCLUSIVE_TIME]: [ 'operation', 'description', 'frequency', 'p75ExclusiveTime', 'sumExclusiveTime', ], [SpanSortPercentiles.P95_EXCLUSIVE_TIME]: [ 'operation', 'description', 'frequency', 'p95ExclusiveTime', 'sumExclusiveTime', ], [SpanSortPercentiles.P99_EXCLUSIVE_TIME]: [ 'operation', 'description', 'frequency', 'p99ExclusiveTime', 'sumExclusiveTime', ], }; const COLUMNS: Record = { operation: { key: 'operation', name: t('Span Operation'), width: COL_WIDTH_UNDEFINED, }, description: { key: 'description', name: t('Span Name'), width: COL_WIDTH_UNDEFINED, }, frequency: { key: 'frequency', name: t('Frequency'), width: COL_WIDTH_UNDEFINED, }, avgOccurrences: { key: 'avgOccurrences', name: t('Average Occurrences'), width: COL_WIDTH_UNDEFINED, }, p50ExclusiveTime: { key: 'p50ExclusiveTime', name: t('P50 Self Time'), width: COL_WIDTH_UNDEFINED, }, p75ExclusiveTime: { key: 'p75ExclusiveTime', name: t('P75 Self Time'), width: COL_WIDTH_UNDEFINED, }, p95ExclusiveTime: { key: 'p95ExclusiveTime', name: t('P95 Self Time'), width: COL_WIDTH_UNDEFINED, }, p99ExclusiveTime: { key: 'p99ExclusiveTime', name: t('P99 Self Time'), width: COL_WIDTH_UNDEFINED, }, sumExclusiveTime: { key: 'sumExclusiveTime', name: t('Total Self Time'), width: COL_WIDTH_UNDEFINED, }, }; const COLUMN_TYPE: Record = { operation: 'string', description: 'string', frequency: 'percentage', avgOccurrences: 'number', p50ExclusiveTime: 'duration', p75ExclusiveTime: 'duration', p95ExclusiveTime: 'duration', p99ExclusiveTime: 'duration', sumExclusiveTime: 'duration', };