Skip to content

Commit 1e65953

Browse files
authored
feat: add optional start end time to span query (#900)
* feat: add optional start end time to span query * feat: add util, comment
1 parent fc7cabb commit 1e65953

File tree

9 files changed

+73
-22
lines changed

9 files changed

+73
-22
lines changed

frontend/app/api/projects/[projectId]/traces/[traceId]/spans/[spanId]/route.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
import { NextResponse } from "next/server";
1+
import { NextRequest, NextResponse } from "next/server";
22
import { prettifyError, ZodError } from "zod/v4";
33

44
import { getSpan, updateSpanOutput } from "@/lib/actions/span";
55

66
export async function GET(
7-
_req: Request,
7+
req: NextRequest,
88
props: { params: Promise<{ projectId: string; traceId: string; spanId: string }> }
99
): Promise<Response> {
1010
const params = await props.params;
1111
const { projectId, traceId, spanId } = params;
12+
const urlSearchParams = req.nextUrl.searchParams;
1213

1314
try {
14-
const span = await getSpan({ spanId, traceId, projectId });
15+
const span = await getSpan({
16+
spanId,
17+
traceId,
18+
projectId,
19+
startTime: urlSearchParams.get("startTime") || undefined,
20+
endTime: urlSearchParams.get("endTime") || undefined,
21+
});
1522

1623
return NextResponse.json(span);
1724
} catch (e) {

frontend/app/api/shared/traces/[traceId]/spans/[spanId]/route.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ import { prettifyError, ZodError } from "zod/v4";
44
import { getSharedSpan } from "@/lib/actions/shared/span";
55

66
export async function GET(
7-
_req: NextRequest,
7+
req: NextRequest,
88
props: { params: Promise<{ traceId: string; spanId: string }> }
99
): Promise<Response> {
1010
const params = await props.params;
11+
const urlSearchParams = req.nextUrl.searchParams;
1112
const traceId = params.traceId;
1213
const spanId = params.spanId;
1314

1415
try {
15-
const span = await getSharedSpan({ traceId, spanId });
16+
const span = await getSharedSpan({
17+
traceId,
18+
spanId,
19+
startTime: urlSearchParams.get("startTime") || undefined,
20+
endTime: urlSearchParams.get("endTime") || undefined,
21+
});
1622
return NextResponse.json(span);
1723
} catch (e) {
1824
if (e instanceof ZodError) {

frontend/components/shared/traces/span-view.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SpanTypeIcon from "@/components/traces/span-type-icon";
77
import SpanMessages from "@/components/traces/span-view/span-content";
88
import { SpanViewStateProvider } from "@/components/traces/span-view/span-view-store";
99
import SpanStatsShields from "@/components/traces/stats-shields";
10+
import { TraceViewTrace } from "@/components/traces/trace-view/trace-view-store.tsx";
1011
import Formatter from "@/components/ui/formatter";
1112
import MonoWithCopy from "@/components/ui/mono-with-copy";
1213
import { Skeleton } from "@/components/ui/skeleton";
@@ -18,12 +19,12 @@ import { swrFetcher } from "@/lib/utils";
1819

1920
interface SpanViewProps {
2021
spanId: string;
21-
traceId: string;
22+
trace: TraceViewTrace;
2223
}
2324

24-
export function SpanView({ spanId, traceId }: SpanViewProps) {
25-
const { data: span, isLoading } = useSWR<Span>(`/api/shared/traces/${traceId}/spans/${spanId}`, swrFetcher);
26-
const { data: events = [] } = useSWR<Event[]>(`/api/shared/traces/${traceId}/spans/${spanId}/events`, swrFetcher);
25+
export function SpanView({ spanId, trace }: SpanViewProps) {
26+
const { data: span, isLoading } = useSWR<Span>(`/api/shared/traces/${trace.id}/spans/${spanId}`, swrFetcher);
27+
const { data: events = [] } = useSWR<Event[]>(`/api/shared/traces/${trace.id}/spans/${spanId}/events`, swrFetcher);
2728

2829
const cleanedEvents = useMemo(
2930
() =>

frontend/components/shared/traces/trace-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ const PureTraceView = ({ trace, spans }: TraceViewProps) => {
234234
</div>
235235
{selectedSpan && (
236236
<div className="flex-grow overflow-hidden flex-wrap">
237-
<SpanView key={selectedSpan.spanId} spanId={selectedSpan.spanId} traceId={trace.id} />
237+
<SpanView key={selectedSpan.spanId} spanId={selectedSpan.spanId} trace={trace} />
238238
</div>
239239
)}
240240
</ResizablePanel>

frontend/components/traces/span-view/index.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import useSWR from "swr";
77
import { SpanControls } from "@/components/traces/span-controls";
88
import SpanContent from "@/components/traces/span-view/span-content";
99
import { SpanViewStateProvider } from "@/components/traces/span-view/span-view-store";
10+
import { TraceViewTrace } from "@/components/traces/trace-view/trace-view-store.tsx";
1011
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
1112
import CodeHighlighter from "@/components/ui/code-highlighter/index";
1213
import { Skeleton } from "@/components/ui/skeleton";
@@ -17,7 +18,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
1718

1819
interface SpanViewProps {
1920
spanId: string;
20-
traceId: string;
21+
trace: TraceViewTrace;
2122
}
2223

2324
const swrFetcher = async (url: string) => {
@@ -32,15 +33,19 @@ const swrFetcher = async (url: string) => {
3233
return res.json();
3334
};
3435

35-
export function SpanView({ spanId, traceId }: SpanViewProps) {
36+
export function SpanView({ spanId, trace }: SpanViewProps) {
3637
const { projectId } = useParams();
38+
const params = new URLSearchParams([
39+
["startTime", trace.startTime],
40+
["endTime", trace.endTime],
41+
]);
3742
const {
3843
data: span,
3944
isLoading,
4045
error,
41-
} = useSWR<Span>(`/api/projects/${projectId}/traces/${traceId}/spans/${spanId}`, swrFetcher);
46+
} = useSWR<Span>(`/api/projects/${projectId}/traces/${trace.id}/spans/${spanId}?${params}`, swrFetcher);
4247
const { data: events } = useSWR<Event[]>(
43-
`/api/projects/${projectId}/traces/${traceId}/spans/${spanId}/events`,
48+
`/api/projects/${projectId}/traces/${trace.id}/spans/${spanId}/events`,
4449
swrFetcher
4550
);
4651

frontend/components/traces/trace-view/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
488488
key={selectedSpan.spanId}
489489
/>
490490
) : (
491-
<SpanView key={selectedSpan.spanId} spanId={selectedSpan.spanId} traceId={traceId} />
491+
<SpanView key={selectedSpan.spanId} spanId={selectedSpan.spanId} trace={trace} />
492492
)
493493
) : (
494494
<div className="flex flex-col items-center justify-center size-full text-muted-foreground">

frontend/lib/actions/shared/span/index.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,30 @@ import { executeQuery } from "@/lib/actions/sql";
66
import { db } from "@/lib/db/drizzle.ts";
77
import { sharedTraces } from "@/lib/db/migrations/schema.ts";
88
import { Span } from "@/lib/traces/types.ts";
9+
import { formatEndTimeForQuery } from "@/lib/utils.ts";
910

1011
export const GetSharedSpanSchema = z.object({
1112
spanId: z.string(),
1213
traceId: z.string(),
14+
startTime: z.string().optional(),
15+
endTime: z.string().optional(),
1316
});
1417

1518
export const getSharedSpan = async (input: z.infer<typeof GetSharedSpanSchema>) => {
16-
const { spanId, traceId } = GetSharedSpanSchema.parse(input);
19+
const { spanId, traceId, startTime, endTime } = GetSharedSpanSchema.parse(input);
20+
21+
const whereConditions = [`span_id = {spanId: UUID}`, `trace_id = {traceId: UUID}`];
22+
const parameters: Record<string, any> = { spanId, traceId };
23+
24+
if (startTime) {
25+
whereConditions.push(`start_time >= {startTime: String}`);
26+
parameters.startTime = startTime.replace("Z", "");
27+
}
28+
29+
if (endTime) {
30+
whereConditions.push(`start_time <= {endTime: String}`);
31+
parameters.endTime = formatEndTimeForQuery(endTime);
32+
}
1733

1834
const sharedTrace = await db.query.sharedTraces.findFirst({
1935
where: eq(sharedTraces.id, traceId),
@@ -45,13 +61,10 @@ export const getSharedSpan = async (input: z.infer<typeof GetSharedSpanSchema>)
4561
path,
4662
attributes
4763
FROM spans
48-
WHERE span_id = {spanId: UUID} AND trace_id = {traceId: UUID}
64+
WHERE ${whereConditions.join(" AND ")}
4965
LIMIT 1
5066
`,
51-
parameters: {
52-
spanId,
53-
traceId,
54-
},
67+
parameters,
5568
projectId: sharedTrace.projectId,
5669
});
5770

frontend/lib/actions/span/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import { executeQuery } from "@/lib/actions/sql";
77
import { clickhouseClient } from "@/lib/clickhouse/client";
88
import { downloadSpanImages } from "@/lib/spans/utils";
99
import { Span } from "@/lib/traces/types.ts";
10+
import { formatEndTimeForQuery } from "@/lib/utils.ts";
1011

1112
export const GetSpanSchema = z.object({
1213
spanId: z.string(),
1314
projectId: z.string(),
1415
traceId: z.string().optional(),
16+
startTime: z.string().optional(),
17+
endTime: z.string().optional(),
1518
});
1619

1720
export const UpdateSpanOutputSchema = z.object({
@@ -41,7 +44,7 @@ export const PushSpanSchema = z.object({
4144
});
4245

4346
export async function getSpan(input: z.infer<typeof GetSpanSchema>) {
44-
const { spanId, traceId, projectId } = GetSpanSchema.parse(input);
47+
const { spanId, traceId, projectId, startTime, endTime } = GetSpanSchema.parse(input);
4548

4649
const whereConditions = [`span_id = {spanId: UUID}`];
4750
const parameters: Record<string, any> = { spanId };
@@ -51,6 +54,16 @@ export async function getSpan(input: z.infer<typeof GetSpanSchema>) {
5154
parameters.traceId = traceId;
5255
}
5356

57+
if (startTime) {
58+
whereConditions.push(`start_time >= {startTime: String}`);
59+
parameters.startTime = startTime.replace("Z", "");
60+
}
61+
62+
if (endTime) {
63+
whereConditions.push(`start_time <= {endTime: String}`);
64+
parameters.endTime = formatEndTimeForQuery(endTime);
65+
}
66+
5467
const mainQuery = `
5568
SELECT
5669
span_id as spanId,

frontend/lib/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,9 @@ export const tryParseJson = (value: string) => {
338338
return null;
339339
}
340340
};
341+
342+
// Add 1 second to end time to cover case with rounding of end time, because of conversion clickhouse and js date time formats.
343+
export const formatEndTimeForQuery = (endTime: string): string => {
344+
const endTimeWithBuffer = new Date(new Date(endTime).getTime() + 1000).toISOString();
345+
return endTimeWithBuffer.replace("Z", "");
346+
};

0 commit comments

Comments
 (0)