Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 71 additions & 14 deletions static/app/views/insights/pages/agents/components/tracesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {Fragment, memo, useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {Tag} from '@sentry/scraps/badge/tag';
import {Flex} from '@sentry/scraps/layout/flex';

import {Button} from 'sentry/components/core/button';
import {Text} from 'sentry/components/core/text';
import {Tooltip} from 'sentry/components/core/tooltip';
import Pagination from 'sentry/components/pagination';
import Placeholder from 'sentry/components/placeholder';
import GridEditable, {
COL_WIDTH_UNDEFINED,
type GridColumnHeader,
Expand All @@ -18,10 +23,7 @@ import usePageFilters from 'sentry/utils/usePageFilters';
import {SAMPLING_MODE} from 'sentry/views/explore/hooks/useProgressiveQuery';
import {useTraces} from 'sentry/views/explore/hooks/useTraces';
import {getExploreUrl} from 'sentry/views/explore/utils';
import {
OverflowEllipsisTextContainer,
TextAlignRight,
} from 'sentry/views/insights/common/components/textAlign';
import {TextAlignRight} from 'sentry/views/insights/common/components/textAlign';
import {useSpans} from 'sentry/views/insights/common/queries/useDiscover';
import {useTraceViewDrawer} from 'sentry/views/insights/pages/agents/components/drawer';
import {LLMCosts} from 'sentry/views/insights/pages/agents/components/llmCosts';
Expand All @@ -40,6 +42,7 @@ import {DurationCell} from 'sentry/views/insights/pages/platform/shared/table/Du
import {NumberCell} from 'sentry/views/insights/pages/platform/shared/table/NumberCell';

interface TableData {
agents: string[];
duration: number;
errors: number;
llmCalls: number;
Expand All @@ -49,14 +52,15 @@ interface TableData {
totalTokens: number;
traceId: string;
transaction: string;
isAgentDataLoading?: boolean;
isSpanDataLoading?: boolean;
}

const EMPTY_ARRAY: never[] = [];

const defaultColumnOrder: Array<GridColumnOrder<string>> = [
{key: 'traceId', name: t('Trace ID'), width: 110},
{key: 'transaction', name: t('Trace Root'), width: COL_WIDTH_UNDEFINED},
{key: 'agents', name: t('Agents'), width: COL_WIDTH_UNDEFINED},
{key: 'duration', name: t('Root Duration'), width: 130},
{key: 'errors', name: t('Errors'), width: 100},
{key: 'llmCalls', name: t('LLM Calls'), width: 110},
Expand Down Expand Up @@ -112,9 +116,32 @@ export function TracesTable() {
Referrer.TRACES_TABLE
);

const agentsRequest = useSpans(
{
search: `span.op:gen_ai.invoke_agent has:gen_ai.agent.name trace:[${tracesRequest.data?.data.map(span => `"${span.trace}"`).join(',')}]`,
fields: ['trace', 'gen_ai.agent.name', 'timestamp'],
sorts: [{field: 'timestamp', kind: 'asc'}],
samplingMode: SAMPLING_MODE.HIGH_ACCURACY,
enabled: Boolean(tracesRequest.data && tracesRequest.data.data.length > 0),
limit: 100,
},
Referrer.TRACES_TABLE
);

const traceAgents = useMemo<Map<string, Set<string>>>(() => {
if (!agentsRequest.data) {
return new Map();
}
return agentsRequest.data.reduce((acc, span) => {
const agentsSet = acc.get(span.trace) ?? new Set();
agentsSet.add(span['gen_ai.agent.name']);
acc.set(span.trace, agentsSet);
return acc;
}, new Map<string, Set<string>>());
}, [agentsRequest.data]);

const traceErrorRequest = useSpans(
{
// Get all spans with error status
search: `span.status:internal_error trace:[${tracesRequest.data?.data.map(span => span.trace).join(',')}]`,
fields: ['trace', 'count(span.duration)'],
limit: tracesRequest.data?.data.length ?? 0,
Expand Down Expand Up @@ -175,21 +202,25 @@ export function TracesTable() {
totalTokens: spanDataMap[span.trace]?.totalTokens ?? 0,
totalCost: spanDataMap[span.trace]?.totalCost ?? null,
timestamp: span.start,
agents: Array.from(traceAgents.get(span.trace) ?? []),
isAgentDataLoading: agentsRequest.isLoading,
isSpanDataLoading: spansRequest.isLoading || traceErrorRequest.isLoading,
}));
}, [
tracesRequest.data,
spanDataMap,
spansRequest.isLoading,
traceErrorRequest.isLoading,
traceAgents,
agentsRequest.isLoading,
]);

const renderHeadCell = useCallback((column: GridColumnHeader<string>) => {
return (
<HeadCell align={rightAlignColumns.has(column.key) ? 'right' : 'left'}>
{column.name}
{column.key === 'timestamp' && <IconArrow direction="down" size="xs" />}
{column.key === 'transaction' && <CellExpander />}
{column.key === 'agents' && <CellExpander />}
</HeadCell>
);
}, []);
Expand All @@ -208,9 +239,9 @@ export function TracesTable() {
isLoading={tracesRequest.isPending}
error={tracesRequest.error}
data={tableData}
stickyHeader
columnOrder={columnOrder}
columnSortBy={EMPTY_ARRAY}
stickyHeader
grid={{
renderBodyCell,
renderHeadCell,
Expand All @@ -237,6 +268,8 @@ const BodyCell = memo(function BodyCell({
const {selection} = usePageFilters();
const {openTraceViewDrawer} = useTraceViewDrawer();

const agentFlow = dataRow.agents.join(', ');

switch (column.key) {
case 'traceId':
return (
Expand All @@ -251,13 +284,37 @@ const BodyCell = memo(function BodyCell({
</TraceIdButton>
</span>
);
case 'transaction':
return (
<Tooltip title={dataRow.transaction} showOnlyOnOverflow skipWrapper>
<OverflowEllipsisTextContainer>
{dataRow.transaction}
</OverflowEllipsisTextContainer>
case 'agents':
if (dataRow.isAgentDataLoading) {
return <Placeholder width="100%" height="16px" />;
}
return agentFlow.length > 0 ? (
<Tooltip
title={
<Flex align="start" direction="column" gap="sm" overflow="hidden">
{dataRow.agents.map(agent => (
<Tag key={agent} type="default">
{agent}
</Tag>
))}
</Flex>
}
maxWidth={500}
showOnlyOnOverflow
skipWrapper
>
<Flex align="start" direction="row" gap="sm" overflow="hidden">
{dataRow.agents.map(agent => (
<Tag key={agent} type="default">
{agent}
</Tag>
))}
</Flex>
</Tooltip>
) : (
<Text size="sm" variant="muted">
{t('(no value)')}
</Text>
);
case 'duration':
return <DurationCell milliseconds={dataRow.duration} />;
Expand Down
Loading