import type {Location} from 'history'; import GridEditable, { COL_WIDTH_UNDEFINED, type GridColumnHeader, } from 'sentry/components/gridEditable'; import Link from 'sentry/components/links/link'; import {t} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; import EventView, {type EventsMetaType} from 'sentry/utils/discover/eventView'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; import type {Sort} from 'sentry/utils/discover/fields'; import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry'; import {decodeScalar, decodeSorts} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell'; import {useIndexedSpans} from 'sentry/views/starfish/queries/useIndexedSpans'; import {SpanIndexedField} from 'sentry/views/starfish/types'; import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters'; type Column = GridColumnHeader< | SpanIndexedField.ID | SpanIndexedField.SPAN_DURATION | SpanIndexedField.TIMESTAMP | SpanIndexedField.USER >; const COLUMN_ORDER: Column[] = [ { key: SpanIndexedField.ID, name: t('Span ID'), width: COL_WIDTH_UNDEFINED, }, { key: SpanIndexedField.USER, name: t('User'), width: COL_WIDTH_UNDEFINED, }, { key: SpanIndexedField.TIMESTAMP, name: t('Timestamp'), width: COL_WIDTH_UNDEFINED, }, { key: SpanIndexedField.SPAN_DURATION, name: t('Total duration'), width: 150, }, ]; const SORTABLE_FIELDS = [ SpanIndexedField.ID, SpanIndexedField.SPAN_DURATION, SpanIndexedField.TIMESTAMP, ]; type ValidSort = Sort & { field: | SpanIndexedField.ID | SpanIndexedField.SPAN_DURATION | SpanIndexedField.TIMESTAMP; }; export function isAValidSort(sort: Sort): sort is ValidSort { return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field); } interface Props { groupId: string; } export function PipelineSpansTable({groupId}: Props) { const location = useLocation(); const organization = useOrganization(); const sortField = decodeScalar(location.query?.[QueryParameterNames.SPANS_SORT]); let sort = decodeSorts(sortField).filter(isAValidSort)[0]; if (!sort) { sort = {field: SpanIndexedField.TIMESTAMP, kind: 'desc'}; } const { data: rawData, meta: rawMeta, error, isLoading, } = useIndexedSpans({ limit: 30, sorts: [sort], fields: [ SpanIndexedField.ID, SpanIndexedField.TRACE, SpanIndexedField.SPAN_DURATION, SpanIndexedField.TRANSACTION_ID, SpanIndexedField.USER, SpanIndexedField.TIMESTAMP, SpanIndexedField.PROJECT, ], referrer: 'api.ai-pipelines.view', search: new MutableSearch(`span.category:ai.pipeline span.group:"${groupId}"`), }); const data = rawData || []; const meta = rawMeta as EventsMetaType; return ( 0} isLoading={isLoading} > renderHeadCell({ column, sort, location, sortParameterName: QueryParameterNames.SPANS_SORT, }), renderBodyCell: (column, row) => renderBodyCell(column, row, meta, location, organization), }} location={location} /> ); } function renderBodyCell( column: Column, row: any, meta: EventsMetaType | undefined, location: Location, organization: Organization ) { if (column.key === SpanIndexedField.ID) { if (!row[SpanIndexedField.ID]) { return (unknown); } if (!row[SpanIndexedField.TRACE]) { return {row[SpanIndexedField.ID]}; } return ( {row[SpanIndexedField.ID]} ); } if (!meta || !meta?.fields) { return row[column.key]; } const renderer = getFieldRenderer(column.key, meta.fields, false); const rendered = renderer(row, { location, organization, unit: meta.units?.[column.key], }); return rendered; }