Skip to content

Commit aad5b6e

Browse files
authored
feat: event definitions / events / trigger spans / settings (#953)
* feat: add events WIP * feat: events * feat: events v0 WIP * feat: fix tooltip * feat: complete event definition management * feat: add settings trigger spans * feat: update settings ui * feat: events WIP * feat: update ui, add proper types, last event query * feat: update migration * feat: update error in schema * feat: add try parse * feat: update ui, add togglable structured output, trace view * feat: add cache cleanup, fix comment * feat: update ui, hide events feature for free users, update queries * feat: hide event crud for free users * fix: missing semantic true * feat: update wording, icon * feat: fix placeholder
1 parent 227f9cf commit aad5b6e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+7721
-987
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { prettifyError, ZodError } from "zod/v4";
3+
4+
import { deleteEventDefinition, getEventDefinition, updateEventDefinition } from "@/lib/actions/event-definitions";
5+
6+
export async function GET(_request: NextRequest, props: { params: Promise<{ projectId: string; id: string }> }) {
7+
const params = await props.params;
8+
const { id, projectId } = params;
9+
10+
try {
11+
const result = await getEventDefinition({ id, projectId });
12+
13+
if (!result) {
14+
return NextResponse.json({ error: "Event definition not found" }, { status: 404 });
15+
}
16+
return NextResponse.json(result);
17+
} catch (error) {
18+
if (error instanceof ZodError) {
19+
return Response.json({ error: prettifyError(error) }, { status: 400 });
20+
}
21+
return NextResponse.json(
22+
{ error: error instanceof Error ? error.message : "Failed to fetch event definition." },
23+
{ status: 500 }
24+
);
25+
}
26+
}
27+
28+
export async function PUT(request: NextRequest, props: { params: Promise<{ projectId: string; id: string }> }) {
29+
const params = await props.params;
30+
const { projectId, id } = params;
31+
32+
try {
33+
const body = await request.json();
34+
35+
const result = await updateEventDefinition({ projectId, id, ...body });
36+
37+
if (!result) {
38+
return NextResponse.json({ error: "Event definition not found" }, { status: 404 });
39+
}
40+
41+
return NextResponse.json(result);
42+
} catch (error) {
43+
if (error instanceof ZodError) {
44+
return Response.json({ error: prettifyError(error) }, { status: 400 });
45+
}
46+
return NextResponse.json(
47+
{ error: error instanceof Error ? error.message : "Failed to update event definition." },
48+
{ status: 500 }
49+
);
50+
}
51+
}
52+
53+
export async function DELETE(_request: NextRequest, props: { params: Promise<{ projectId: string; id: string }> }) {
54+
const params = await props.params;
55+
const { projectId, id } = params;
56+
57+
try {
58+
const result = await deleteEventDefinition({ projectId, id });
59+
60+
if (!result) {
61+
return NextResponse.json({ error: "Event definition not found" }, { status: 404 });
62+
}
63+
64+
return NextResponse.json(result);
65+
} catch (error) {
66+
if (error instanceof ZodError) {
67+
return Response.json({ error: prettifyError(error) }, { status: 400 });
68+
}
69+
return NextResponse.json(
70+
{ error: error instanceof Error ? error.message : "Failed to delete event definition." },
71+
{ status: 500 }
72+
);
73+
}
74+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { prettifyError, ZodError } from "zod/v4";
3+
4+
import { createEventDefinition, getEventDefinitions } from "@/lib/actions/event-definitions";
5+
6+
export async function GET(_request: NextRequest, props: { params: Promise<{ projectId: string }> }) {
7+
const params = await props.params;
8+
const projectId = params.projectId;
9+
10+
try {
11+
const result = await getEventDefinitions({ projectId });
12+
return NextResponse.json(result);
13+
} catch (error) {
14+
if (error instanceof ZodError) {
15+
return Response.json({ error: prettifyError(error) }, { status: 400 });
16+
}
17+
return NextResponse.json(
18+
{ error: error instanceof Error ? error.message : "Failed to fetch event definitions." },
19+
{ status: 500 }
20+
);
21+
}
22+
}
23+
24+
export async function POST(request: NextRequest, props: { params: Promise<{ projectId: string }> }) {
25+
const params = await props.params;
26+
const projectId = params.projectId;
27+
28+
try {
29+
const body = await request.json();
30+
31+
const result = await createEventDefinition({ projectId, ...body });
32+
33+
return NextResponse.json(result);
34+
} catch (error) {
35+
if (error instanceof ZodError) {
36+
return Response.json({ error: prettifyError(error) }, { status: 400 });
37+
}
38+
return NextResponse.json(
39+
{ error: error instanceof Error ? error.message : "Failed to create event definition." },
40+
{ status: 500 }
41+
);
42+
}
43+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { NextRequest } from "next/server";
2+
import { prettifyError, ZodError } from "zod/v4";
3+
4+
import { parseUrlParams } from "@/lib/actions/common/utils";
5+
import { getEventsPaginated, GetEventsPaginatedSchema } from "@/lib/actions/events";
6+
7+
export async function GET(
8+
req: NextRequest,
9+
props: { params: Promise<{ projectId: string; name: string }> }
10+
): Promise<Response> {
11+
const params = await props.params;
12+
const { projectId, name } = params;
13+
const parseResult = parseUrlParams(
14+
req.nextUrl.searchParams,
15+
GetEventsPaginatedSchema.omit({ projectId: true, eventName: true })
16+
);
17+
18+
if (!parseResult.success) {
19+
return Response.json({ error: prettifyError(parseResult.error) }, { status: 400 });
20+
}
21+
22+
try {
23+
const result = await getEventsPaginated({ ...parseResult.data, projectId, eventName: name });
24+
return Response.json(result);
25+
} catch (error) {
26+
if (error instanceof ZodError) {
27+
return Response.json({ error: prettifyError(error) }, { status: 400 });
28+
}
29+
return Response.json(
30+
{ error: error instanceof Error ? error.message : "Failed to fetch events." },
31+
{ status: 500 }
32+
);
33+
}
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { prettifyError, ZodError } from "zod/v4";
3+
4+
import { deleteSummaryTriggerSpan } from "@/lib/actions/summary-trigger-spans";
5+
6+
export async function DELETE(_request: NextRequest, props: { params: Promise<{ projectId: string; id: string }> }) {
7+
const params = await props.params;
8+
const { projectId, id } = params;
9+
10+
try {
11+
const result = await deleteSummaryTriggerSpan({ projectId, id });
12+
13+
if (!result) {
14+
return NextResponse.json({ error: "Summary trigger span not found" }, { status: 404 });
15+
}
16+
17+
return NextResponse.json(result);
18+
} catch (error) {
19+
if (error instanceof ZodError) {
20+
return Response.json({ error: prettifyError(error) }, { status: 400 });
21+
}
22+
return NextResponse.json(
23+
{ error: error instanceof Error ? error.message : "Failed to delete summary trigger span." },
24+
{ status: 500 }
25+
);
26+
}
27+
}
28+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { prettifyError, ZodError } from "zod/v4";
3+
4+
import { createSummaryTriggerSpan, getSummaryTriggerSpans } from "@/lib/actions/summary-trigger-spans";
5+
6+
export async function GET(_request: NextRequest, props: { params: Promise<{ projectId: string }> }) {
7+
const params = await props.params;
8+
const projectId = params.projectId;
9+
10+
try {
11+
const result = await getSummaryTriggerSpans({ projectId });
12+
return NextResponse.json(result);
13+
} catch (error) {
14+
if (error instanceof ZodError) {
15+
return Response.json({ error: prettifyError(error) }, { status: 400 });
16+
}
17+
return NextResponse.json(
18+
{ error: error instanceof Error ? error.message : "Failed to fetch summary trigger spans." },
19+
{ status: 500 }
20+
);
21+
}
22+
}
23+
24+
export async function POST(request: NextRequest, props: { params: Promise<{ projectId: string }> }) {
25+
const params = await props.params;
26+
const projectId = params.projectId;
27+
28+
try {
29+
const body = await request.json();
30+
31+
const result = await createSummaryTriggerSpan({ projectId, ...body });
32+
33+
return NextResponse.json(result);
34+
} catch (error) {
35+
if (error instanceof ZodError) {
36+
return Response.json({ error: prettifyError(error) }, { status: 400 });
37+
}
38+
return NextResponse.json(
39+
{ error: error instanceof Error ? error.message : "Failed to create summary trigger span." },
40+
{ status: 500 }
41+
);
42+
}
43+
}
44+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { prettifyError, ZodError } from "zod/v4";
3+
4+
import { getUnassignedSummaryTriggerSpans } from "@/lib/actions/summary-trigger-spans";
5+
6+
export async function GET(_request: NextRequest, props: { params: Promise<{ projectId: string }> }) {
7+
const params = await props.params;
8+
const projectId = params.projectId;
9+
10+
try {
11+
const result = await getUnassignedSummaryTriggerSpans({ projectId });
12+
return NextResponse.json(result);
13+
} catch (error) {
14+
if (error instanceof ZodError) {
15+
return NextResponse.json({ error: prettifyError(error) }, { status: 400 });
16+
}
17+
return NextResponse.json(
18+
{ error: error instanceof Error ? error.message : "Failed to fetch unassigned summary trigger spans." },
19+
{ status: 500 }
20+
);
21+
}
22+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Metadata } from "next";
2+
import { cookies } from "next/headers";
3+
import { notFound } from "next/navigation";
4+
5+
import Events from "@/components/events/events";
6+
import { EventsStoreProvider } from "@/components/events/events-store";
7+
import { EventDefinition, getEventDefinition } from "@/lib/actions/event-definitions";
8+
import { getLastEvent } from "@/lib/actions/events";
9+
import { EVENTS_TRACE_VIEW_WIDTH } from "@/lib/actions/traces";
10+
11+
export const metadata: Metadata = {
12+
title: "Events",
13+
};
14+
15+
export default async function EventsPage(props: {
16+
params: Promise<{ projectId: string; id: string }>;
17+
searchParams: Promise<{ traceId?: string; spanId?: string }>;
18+
}) {
19+
const { projectId, id } = await props.params;
20+
const { traceId, spanId } = await props.searchParams;
21+
22+
const eventDefinition = (await getEventDefinition({ projectId, id })) as EventDefinition | undefined;
23+
if (!eventDefinition) {
24+
return notFound();
25+
}
26+
27+
const lastEvent = await getLastEvent({ projectId, name: eventDefinition.name });
28+
29+
const cookieStore = await cookies();
30+
const traceViewWidthCookie = cookieStore.get(EVENTS_TRACE_VIEW_WIDTH);
31+
const initialTraceViewWidth = traceViewWidthCookie ? parseInt(traceViewWidthCookie.value, 10) : undefined;
32+
33+
return (
34+
<EventsStoreProvider eventDefinition={eventDefinition} traceId={traceId} spanId={spanId}>
35+
<Events lastEvent={lastEvent} initialTraceViewWidth={initialTraceViewWidth} />
36+
</EventsStoreProvider>
37+
);
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Metadata } from "next";
2+
3+
import EventDefinitions from "@/components/event-definitions/event-definitions";
4+
import { EventDefinitionsStoreProvider } from "@/components/event-definitions/event-definitions-store";
5+
6+
export const metadata: Metadata = {
7+
title: "Events",
8+
};
9+
10+
export default async function EventsPage(props: { params: Promise<{ projectId: string }> }) {
11+
const { projectId } = await props.params;
12+
13+
return (
14+
<EventDefinitionsStoreProvider projectId={projectId}>
15+
<EventDefinitions />
16+
</EventDefinitionsStoreProvider>
17+
);
18+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { ColumnDef } from "@tanstack/react-table";
2+
import React from "react";
3+
4+
import ClientTimestampFormatter from "@/components/client-timestamp-formatter.tsx";
5+
import { Badge } from "@/components/ui/badge.tsx";
6+
import { EventDefinitionRow } from "@/lib/actions/event-definitions";
7+
8+
export const columns: ColumnDef<EventDefinitionRow>[] = [
9+
{
10+
header: "Name",
11+
accessorFn: (row) => row.name,
12+
cell: ({ row }) => <span className="truncate">{row.original.name}</span>,
13+
},
14+
{
15+
header: "Trigger Spans",
16+
id: "triggerSpans",
17+
accessorFn: (row) => row.triggerSpans,
18+
cell: (row) => {
19+
const spans = row.getValue() as string[];
20+
21+
if (spans?.length > 0) {
22+
return (
23+
<>
24+
{spans.map((span) => (
25+
<Badge key={span} className="rounded-3xl mr-1" variant="outline">
26+
<span>{span}</span>
27+
</Badge>
28+
))}
29+
</>
30+
);
31+
}
32+
return "-";
33+
},
34+
},
35+
{
36+
header: "Semantic",
37+
accessorFn: (row) => row.isSemantic,
38+
cell: ({ row }) => (row.original.isSemantic ? "Yes" : "No"),
39+
},
40+
{
41+
header: "Created At",
42+
accessorFn: (row) => row.createdAt,
43+
cell: ({ row }) => <ClientTimestampFormatter timestamp={row.original.createdAt} />,
44+
},
45+
];

0 commit comments

Comments
 (0)