Files
triggerdotdev--trigger.dev/apps/webapp/app/components/runs/v3/SpanEvents.tsx
T
2026-07-13 13:32:57 +08:00

124 lines
3.4 KiB
TypeScript

import { EnvelopeIcon } from "@heroicons/react/20/solid";
import {
exceptionEventEnhancer,
isExceptionSpanEvent,
type ExceptionEventProperties,
type SpanEvent as OtelSpanEvent,
} from "@trigger.dev/core/v3";
import { CodeBlock } from "~/components/code/CodeBlock";
import { Feedback } from "~/components/Feedback";
import { Button } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { DateTimeAccurate } from "~/components/primitives/DateTime";
import { Header3 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
type SpanEventsProps = {
spanEvents: OtelSpanEvent[];
};
export function SpanEvents({ spanEvents }: SpanEventsProps) {
const displayableEvents = spanEvents.filter((event) => !event.name.startsWith("trigger.dev/"));
if (displayableEvents.length === 0) {
return null;
}
return (
<div className="flex flex-col gap-4">
{displayableEvents.map((event, index) => (
<SpanEvent key={index} spanEvent={event} />
))}
</div>
);
}
function SpanEventHeader({
title,
titleClassName,
time,
}: {
title: string;
titleClassName?: string;
time: Date;
}) {
return (
<div className="flex items-center justify-between">
<Header3 className={titleClassName}>{title}</Header3>
<Paragraph variant="extra-small">
<DateTimeAccurate date={time} />
</Paragraph>
</div>
);
}
function SpanEvent({ spanEvent }: { spanEvent: OtelSpanEvent }) {
if (isExceptionSpanEvent(spanEvent)) {
return <SpanEventError spanEvent={spanEvent} exception={spanEvent.properties.exception} />;
}
return (
<div className="flex flex-col gap-2">
<SpanEventHeader title={spanEvent.name} time={spanEvent.time} />
{spanEvent.properties && (
<CodeBlock code={JSON.stringify(spanEvent.properties, null, 2)} maxLines={20} />
)}
</div>
);
}
export function SpanEventError({
spanEvent,
exception,
}: {
spanEvent: OtelSpanEvent;
exception: ExceptionEventProperties;
}) {
const enhancedException = exceptionEventEnhancer(exception);
return (
<div className="flex flex-col gap-2 rounded-sm border border-rose-500/50 px-3 pb-3 pt-2">
<SpanEventHeader
title={enhancedException.type ?? "Error"}
time={spanEvent.time}
titleClassName="text-rose-500"
/>
{enhancedException.message && (
<Callout variant="error">
<pre className="text-wrap font-sans text-sm font-normal text-rose-200">
{enhancedException.message}
</pre>
</Callout>
)}
{enhancedException.link &&
(enhancedException.link.magic === "CONTACT_FORM" ? (
<Feedback
button={
<Button
variant="tertiary/medium"
LeadingIcon={EnvelopeIcon}
leadingIconClassName="text-blue-400"
fullWidth
textAlignLeft
>
{enhancedException.link.name}
</Button>
}
/>
) : (
<Callout variant="docs" to={enhancedException.link.href}>
{enhancedException.link.name}
</Callout>
))}
{enhancedException.stacktrace && (
<CodeBlock
showCopyButton={false}
showLineNumbers={false}
code={enhancedException.stacktrace}
maxLines={20}
/>
)}
</div>
);
}