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;
}