mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
fix: get span list data from API
This commit is contained in:
parent
3d4c6eda71
commit
0976a572e3
@ -1,11 +1,14 @@
|
||||
import './SpanList.styles.scss';
|
||||
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useMemo } from 'react';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
|
||||
import { mockEntrySpanData } from './mockData';
|
||||
import SearchFilters from './SearchFilters';
|
||||
import SpanTable from './SpanTable';
|
||||
import { SpanDataRow } from './types';
|
||||
import { transformEntrySpansToHierarchy } from './utils';
|
||||
|
||||
interface SpanListProps {
|
||||
@ -14,9 +17,90 @@ interface SpanListProps {
|
||||
}
|
||||
|
||||
function SpanList({ traceId, setSelectedSpan }: SpanListProps): JSX.Element {
|
||||
const payload = initialQueriesMap.traces;
|
||||
|
||||
const { data, isLoading, isFetching } = useGetQueryRange(
|
||||
{
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
query: {
|
||||
...payload,
|
||||
builder: {
|
||||
...payload.builder,
|
||||
queryData: [
|
||||
{
|
||||
...payload.builder.queryData[0],
|
||||
...{
|
||||
name: 'A',
|
||||
signal: 'traces',
|
||||
stepInterval: null,
|
||||
disabled: false,
|
||||
filter: {
|
||||
expression: `trace_id = '${traceId}' isEntryPoint = 'true'`,
|
||||
},
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
order: [
|
||||
{
|
||||
key: {
|
||||
name: 'timestamp',
|
||||
},
|
||||
direction: 'desc',
|
||||
},
|
||||
],
|
||||
having: {
|
||||
expression: '',
|
||||
},
|
||||
selectFields: [
|
||||
{
|
||||
name: 'service.name',
|
||||
fieldDataType: 'string',
|
||||
signal: 'traces',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
fieldDataType: 'string',
|
||||
signal: 'traces',
|
||||
},
|
||||
{
|
||||
name: 'duration_nano',
|
||||
fieldDataType: '',
|
||||
signal: 'traces',
|
||||
fieldContext: 'span',
|
||||
},
|
||||
{
|
||||
name: 'http_method',
|
||||
fieldDataType: '',
|
||||
signal: 'traces',
|
||||
fieldContext: 'span',
|
||||
},
|
||||
{
|
||||
name: 'response_status_code',
|
||||
fieldDataType: '',
|
||||
signal: 'traces',
|
||||
fieldContext: 'span',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// version,
|
||||
ENTITY_VERSION_V5,
|
||||
);
|
||||
|
||||
const hierarchicalData = useMemo(
|
||||
() => transformEntrySpansToHierarchy(mockEntrySpanData),
|
||||
[],
|
||||
() =>
|
||||
// TODO(shaheer): properly fix the type
|
||||
transformEntrySpansToHierarchy(
|
||||
(data?.payload.data.newResult.data.result[0].list as unknown) as
|
||||
| SpanDataRow[]
|
||||
| undefined,
|
||||
),
|
||||
[data?.payload.data.newResult.data.result],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -27,6 +111,7 @@ function SpanList({ traceId, setSelectedSpan }: SpanListProps): JSX.Element {
|
||||
<div className="span-list__content">
|
||||
<SpanTable
|
||||
data={hierarchicalData}
|
||||
isLoading={isLoading || isFetching}
|
||||
traceId={traceId}
|
||||
setSelectedSpan={setSelectedSpan}
|
||||
/>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { ColumnDef, DataTable, Row } from '@signozhq/table';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import { ChevronDownIcon, ChevronRightIcon } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
|
||||
import { HierarchicalSpanData, ServiceEntrySpan, SpanDataRow } from './types';
|
||||
import { fetchServiceSpans, formatDuration } from './utils';
|
||||
import { fetchServiceSpans } from './utils';
|
||||
|
||||
// Constants
|
||||
const SPAN_TYPE_ENTRY = 'entry-span';
|
||||
@ -15,6 +16,7 @@ interface SpanTableProps {
|
||||
data: HierarchicalSpanData;
|
||||
traceId?: string;
|
||||
setSelectedSpan?: (span: Span) => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
interface TableRowData {
|
||||
@ -36,6 +38,7 @@ function SpanTable({
|
||||
data,
|
||||
traceId,
|
||||
setSelectedSpan,
|
||||
isLoading,
|
||||
}: SpanTableProps): JSX.Element {
|
||||
const [expandedEntrySpans, setExpandedEntrySpans] = useState<
|
||||
Record<string, ServiceEntrySpan>
|
||||
@ -156,17 +159,6 @@ function SpanTable({
|
||||
[],
|
||||
);
|
||||
|
||||
const renderSpanCountCell = useCallback(
|
||||
({ row }: { row: Row<TableRowData> }): JSX.Element => {
|
||||
const { original } = row;
|
||||
if (original.type === SPAN_TYPE_ENTRY && original.spanCount !== undefined) {
|
||||
return <span>{original.spanCount}</span>;
|
||||
}
|
||||
return <span>-</span>;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const renderDurationCell = useCallback(
|
||||
({ row }: { row: Row<TableRowData> }): JSX.Element => {
|
||||
const { original } = row;
|
||||
@ -190,7 +182,15 @@ function SpanTable({
|
||||
const renderStatusCodeCell = useCallback(
|
||||
({ row }: { row: Row<TableRowData> }): JSX.Element => {
|
||||
const { original } = row;
|
||||
return <span>{original.statusCode}</span>;
|
||||
return <span>{original.statusCode || '-'}</span>;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const renderSpanIdCell = useCallback(
|
||||
({ row }: { row: Row<TableRowData> }): JSX.Element => {
|
||||
const { original } = row;
|
||||
return <span className="span-id">{original.spanId}</span>;
|
||||
},
|
||||
[],
|
||||
);
|
||||
@ -218,6 +218,13 @@ function SpanTable({
|
||||
size: 180,
|
||||
cell: renderTimestampCell,
|
||||
},
|
||||
{
|
||||
id: 'spanId',
|
||||
header: 'Span ID',
|
||||
accessorKey: 'spanId',
|
||||
size: 150,
|
||||
cell: renderSpanIdCell,
|
||||
},
|
||||
{
|
||||
id: 'httpMethod',
|
||||
header: 'Method',
|
||||
@ -239,13 +246,6 @@ function SpanTable({
|
||||
size: 120,
|
||||
cell: renderServiceCell,
|
||||
},
|
||||
{
|
||||
id: 'spans',
|
||||
header: 'Spans',
|
||||
accessorKey: 'spanCount',
|
||||
size: 80,
|
||||
cell: renderSpanCountCell,
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
header: 'Duration',
|
||||
@ -272,7 +272,10 @@ function SpanTable({
|
||||
spanName: entrySpan.spanData.data.name,
|
||||
serviceName: entrySpan.serviceName,
|
||||
spanCount: spanCount > 0 ? spanCount : undefined,
|
||||
duration: formatDuration(entrySpan.spanData.data.duration_nano),
|
||||
duration: getYAxisFormattedValue(
|
||||
entrySpan.spanData.data.duration_nano.toString(),
|
||||
'ns',
|
||||
),
|
||||
timestamp: entrySpan.spanData.timestamp,
|
||||
statusCode: entrySpan.spanData.data.response_status_code,
|
||||
httpMethod: entrySpan.spanData.data.http_method,
|
||||
@ -288,7 +291,10 @@ function SpanTable({
|
||||
type: SPAN_TYPE_SERVICE,
|
||||
spanName: serviceSpan.data.name,
|
||||
serviceName: serviceSpan.data['service.name'],
|
||||
duration: formatDuration(serviceSpan.data.duration_nano),
|
||||
duration: getYAxisFormattedValue(
|
||||
serviceSpan.data.duration_nano.toString(),
|
||||
'ns',
|
||||
),
|
||||
timestamp: serviceSpan.timestamp,
|
||||
statusCode: serviceSpan.data.response_status_code,
|
||||
httpMethod: serviceSpan.data.http_method,
|
||||
@ -354,6 +360,7 @@ function SpanTable({
|
||||
fixedHeight={args.fixedHeight}
|
||||
data={flattenedData}
|
||||
onRowClick={handleRowClick}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -362,6 +369,7 @@ function SpanTable({
|
||||
SpanTable.defaultProps = {
|
||||
traceId: undefined,
|
||||
setSelectedSpan: undefined,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
export default SpanTable;
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
import { SpanDataRow } from './types';
|
||||
|
||||
export const mockEntrySpanData: SpanDataRow[] = [
|
||||
{
|
||||
data: {
|
||||
duration_nano: 3878813,
|
||||
http_method: '',
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
name: 'CurrencyService/Convert',
|
||||
response_status_code: '0',
|
||||
'service.name': 'currencyservice',
|
||||
span_id: '8439d7461ab457a2',
|
||||
timestamp: '2025-09-03T11:33:46.060209146Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:46.060209146Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 12485759,
|
||||
http_method: '',
|
||||
name: 'CurrencyService/Convert',
|
||||
response_status_code: '0',
|
||||
'service.name': 'currencyservice',
|
||||
span_id: '7dbf7811106b1049',
|
||||
timestamp: '2025-09-03T11:33:46.058715482Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:46.058715482Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 6950595,
|
||||
http_method: '',
|
||||
name: 'CurrencyService/Convert',
|
||||
response_status_code: '0',
|
||||
'service.name': 'currencyservice',
|
||||
span_id: '9adcdf1baa56d23a',
|
||||
timestamp: '2025-09-03T11:33:46.058319328Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:46.058319328Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 5323696,
|
||||
http_method: '',
|
||||
name: 'CurrencyService/Convert',
|
||||
response_status_code: '0',
|
||||
'service.name': 'currencyservice',
|
||||
span_id: '1a1b01a750dfe4d6',
|
||||
timestamp: '2025-09-03T11:33:46.057323233Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:46.057323233Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 133659,
|
||||
http_method: '',
|
||||
name: 'oteldemo.ProductCatalogService/GetProduct',
|
||||
response_status_code: '0',
|
||||
'service.name': 'productcatalogservice',
|
||||
span_id: '485a987e9dbf8ddd',
|
||||
timestamp: '2025-09-03T11:33:45.963838319Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:45.963838319Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 245000000,
|
||||
http_method: 'GET',
|
||||
name: '/api/checkout',
|
||||
response_status_code: '200',
|
||||
'service.name': 'checkoutservice',
|
||||
span_id: 'abc123def456',
|
||||
timestamp: '2025-09-03T11:33:45.800000000Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:45.800000000Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 150000000,
|
||||
http_method: 'POST',
|
||||
name: '/api/payment',
|
||||
response_status_code: '200',
|
||||
'service.name': 'paymentservice',
|
||||
span_id: 'def456ghi789',
|
||||
timestamp: '2025-09-03T11:33:45.750000000Z',
|
||||
trace_id: '5ad9c01671f0e38582efe03bbf81360a',
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:45.750000000Z',
|
||||
},
|
||||
];
|
||||
@ -1,30 +1,28 @@
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { uniqBy } from 'lodash-es';
|
||||
|
||||
import { HierarchicalSpanData, ServiceEntrySpan, SpanDataRow } from './types';
|
||||
|
||||
export function formatDuration(durationNano: number): string {
|
||||
if (durationNano < 1000) {
|
||||
return `${durationNano}ns`;
|
||||
}
|
||||
if (durationNano < 1000000) {
|
||||
return `${(durationNano / 1000).toFixed(2)}μs`;
|
||||
}
|
||||
if (durationNano < 1000000000) {
|
||||
return `${(durationNano / 1000000).toFixed(2)}ms`;
|
||||
}
|
||||
return `${(durationNano / 1000000000).toFixed(2)}s`;
|
||||
}
|
||||
|
||||
export function transformEntrySpansToHierarchy(
|
||||
entrySpans: SpanDataRow[],
|
||||
entrySpans?: SpanDataRow[],
|
||||
): HierarchicalSpanData {
|
||||
let totalTraceTime = 0;
|
||||
|
||||
if (!entrySpans) {
|
||||
return { entrySpans: [], totalTraceTime: 0 };
|
||||
}
|
||||
|
||||
const uniqueEntrySpans = uniqBy(entrySpans, 'data.span_id');
|
||||
|
||||
// Calculate total trace time from all entry spans
|
||||
entrySpans.forEach((span) => {
|
||||
uniqueEntrySpans.forEach((span) => {
|
||||
totalTraceTime += span.data.duration_nano;
|
||||
});
|
||||
|
||||
// Transform entry spans to ServiceEntrySpan structure
|
||||
const entrySpansList: ServiceEntrySpan[] = entrySpans.map((span) => ({
|
||||
const entrySpansList: ServiceEntrySpan[] = uniqueEntrySpans.map((span) => ({
|
||||
spanData: span,
|
||||
serviceName: span.data['service.name'],
|
||||
isExpanded: false,
|
||||
@ -32,58 +30,101 @@ export function transformEntrySpansToHierarchy(
|
||||
isLoadingServiceSpans: false,
|
||||
}));
|
||||
|
||||
// Sort by timestamp (most recent first)
|
||||
entrySpansList.sort(
|
||||
(a, b) =>
|
||||
new Date(b.spanData.timestamp).getTime() -
|
||||
new Date(a.spanData.timestamp).getTime(),
|
||||
);
|
||||
|
||||
return {
|
||||
entrySpans: entrySpansList,
|
||||
totalTraceTime,
|
||||
};
|
||||
}
|
||||
|
||||
// Mock function to simulate fetching service spans
|
||||
export function fetchServiceSpans(
|
||||
export async function fetchServiceSpans(
|
||||
traceId: string,
|
||||
serviceName: string,
|
||||
): Promise<SpanDataRow[]> {
|
||||
// This would normally make an API call to get spans for the specific service
|
||||
// For now, return mock data filtered by service name
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// Mock response - in real implementation, this would call the API
|
||||
const mockServiceSpans: SpanDataRow[] = [
|
||||
{
|
||||
data: {
|
||||
duration_nano: 1500000,
|
||||
http_method: 'GET',
|
||||
name: `${serviceName}/internal-call-1`,
|
||||
response_status_code: '200',
|
||||
'service.name': serviceName,
|
||||
span_id: `${serviceName}-span-1`,
|
||||
timestamp: '2025-09-03T11:33:46.100000000Z',
|
||||
trace_id: traceId,
|
||||
// Use the same payload structure as in SpanList component but with service-specific filter
|
||||
const payload = initialQueriesMap.traces;
|
||||
|
||||
try {
|
||||
const response = await GetMetricQueryRange(
|
||||
{
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
query: {
|
||||
...payload,
|
||||
builder: {
|
||||
...payload.builder,
|
||||
queryData: [
|
||||
{
|
||||
...payload.builder.queryData[0],
|
||||
...{
|
||||
name: 'A',
|
||||
signal: 'traces',
|
||||
stepInterval: null,
|
||||
disabled: false,
|
||||
filter: {
|
||||
expression: `trace_id = '${traceId}' and service.name = '${serviceName}'`,
|
||||
},
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
order: [
|
||||
{
|
||||
key: {
|
||||
name: 'timestamp',
|
||||
},
|
||||
direction: 'desc',
|
||||
},
|
||||
],
|
||||
having: {
|
||||
expression: '',
|
||||
},
|
||||
selectFields: [
|
||||
{
|
||||
name: 'service.name',
|
||||
fieldDataType: 'string',
|
||||
signal: 'traces',
|
||||
fieldContext: 'resource',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
fieldDataType: 'string',
|
||||
signal: 'traces',
|
||||
},
|
||||
{
|
||||
name: 'duration_nano',
|
||||
fieldDataType: '',
|
||||
signal: 'traces',
|
||||
fieldContext: 'span',
|
||||
},
|
||||
{
|
||||
name: 'http_method',
|
||||
fieldDataType: '',
|
||||
signal: 'traces',
|
||||
fieldContext: 'span',
|
||||
},
|
||||
{
|
||||
name: 'response_status_code',
|
||||
fieldDataType: '',
|
||||
signal: 'traces',
|
||||
fieldContext: 'span',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:46.100000000Z',
|
||||
},
|
||||
{
|
||||
data: {
|
||||
duration_nano: 2500000,
|
||||
http_method: 'POST',
|
||||
name: `${serviceName}/internal-call-2`,
|
||||
response_status_code: '200',
|
||||
'service.name': serviceName,
|
||||
span_id: `${serviceName}-span-2`,
|
||||
timestamp: '2025-09-03T11:33:46.200000000Z',
|
||||
trace_id: traceId,
|
||||
},
|
||||
timestamp: '2025-09-03T11:33:46.200000000Z',
|
||||
},
|
||||
];
|
||||
resolve(mockServiceSpans);
|
||||
}, 500); // Simulate network delay
|
||||
});
|
||||
},
|
||||
ENTITY_VERSION_V5,
|
||||
);
|
||||
|
||||
// Extract spans from the API response using the same path as SpanList component
|
||||
const spans =
|
||||
response?.payload?.data?.newResult?.data?.result?.[0]?.list || [];
|
||||
|
||||
// Transform the API response to SpanDataRow format if needed
|
||||
// The API should return the correct format for traces, but we'll handle any potential transformation
|
||||
return (spans as unknown) as SpanDataRow[];
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch service spans:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user