Skip to content

Commit

Permalink
v3 span details (#906)
Browse files Browse the repository at this point in the history
* WIP on saving the resized state in a cookie

* Progress on remembering the panel width

* The resizable panel now remembers even when closed

* Improvements to the span panel

* Hide integrations in the v3 side menu

* Added “UTC” to the times

* WIP changing how styles work

* Reference unflattenAttributes from the package

* Unflatten now correctly unflattens arrays

* Unflatten typecheck fix

* Improvements to the spans and UI for them

* Added the “events” to a span, including special styling for errors

* Unflatten doesn’t work on an array of Attributes
  • Loading branch information
matt-aitken authored Feb 23, 2024
1 parent dd63fe6 commit 07efef4
Show file tree
Hide file tree
Showing 19 changed files with 504 additions and 116 deletions.
10 changes: 2 additions & 8 deletions apps/webapp/app/components/VersionLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { RuntimeEnvironment } from "~/models/runtimeEnvironment.server";
import {
EnvironmentLabel,
environmentBorderClassName,
environmentColorClassName,
environmentTextClassName,
environmentTitle,
} from "./environments/EnvironmentLabel";
import { cn } from "~/utils/cn";
import { environmentTextClassName, environmentTitle } from "./environments/EnvironmentLabel";

type Environment = Pick<RuntimeEnvironment, "type">;

Expand All @@ -20,7 +14,7 @@ export function VersionLabel({ environment, userName, version }: VersionLabelPro
return (
<div
className={cn(
"flex items-center justify-stretch justify-items-stretch rounded-sm border border-midnight-700 text-xxs"
"inline-flex items-center justify-stretch justify-items-stretch rounded-sm border border-midnight-700 text-xxs"
)}
>
<div className="px-1 text-xs tabular-nums text-dimmed">v{version}</div>
Expand Down
16 changes: 9 additions & 7 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,15 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
leadingIconClassName="text-indigo-500"
/>
</SideMenuHeader>
<SideMenuItem
name="Integrations"
icon="integration"
to={organizationIntegrationsPath(organization)}
data-action="integrations"
hasWarning={organization.hasUnconfiguredIntegrations}
/>
{project.version === "V2" && (
<SideMenuItem
name="Integrations"
icon="integration"
to={organizationIntegrationsPath(organization)}
data-action="integrations"
hasWarning={organization.hasUnconfiguredIntegrations}
/>
)}
<SideMenuItem
name="Projects"
icon="folder"
Expand Down
3 changes: 1 addition & 2 deletions apps/webapp/app/components/primitives/Resizable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import { GripVertical } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "~/utils/cn";

Expand Down Expand Up @@ -41,4 +40,4 @@ const ResizableHandle = ({
</ResizablePrimitive.PanelResizeHandle>
);

export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
38 changes: 0 additions & 38 deletions apps/webapp/app/components/runs/v3/EventText.tsx

This file was deleted.

5 changes: 4 additions & 1 deletion apps/webapp/app/components/runs/v3/LiveTimer.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { formatDuration } from "@trigger.dev/core/v3";
import { useState, useEffect } from "react";
import { Paragraph } from "~/components/primitives/Paragraph";
import { cn } from "~/utils/cn";

export function LiveTimer({
startTime,
endTime,
updateInterval = 250,
className,
}: {
startTime: Date;
endTime?: Date;
updateInterval?: number;
className?: string;
}) {
const [now, setNow] = useState<Date>();

Expand All @@ -27,7 +30,7 @@ export function LiveTimer({
}, [startTime]);

return (
<Paragraph variant="extra-small" className="whitespace-nowrap tabular-nums">
<Paragraph variant="extra-small" className={cn("whitespace-nowrap tabular-nums", className)}>
{formatDuration(startTime, now, {
style: "short",
maxDecimalPoints: 0,
Expand Down
70 changes: 70 additions & 0 deletions apps/webapp/app/components/runs/v3/SpanEvents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { CodeBlock } from "~/components/code/CodeBlock";
import { Callout } from "~/components/primitives/Callout";
import { DateTime } from "~/components/primitives/DateTime";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import type { OtelExceptionProperty, OtelSpanEvent } from "~/presenters/v3/SpanPresenter.server";

type SpanEventsProps = {
spanEvents: OtelSpanEvent[];
};

export function SpanEvents({ spanEvents }: SpanEventsProps) {
return (
<div className="flex flex-col gap-4">
{spanEvents.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">
<Header2 className={titleClassName}>{title}</Header2>
<Paragraph variant="small">
<DateTime date={time} />
</Paragraph>
</div>
);
}

function SpanEvent({ spanEvent }: { spanEvent: OtelSpanEvent }) {
if (spanEvent.properties?.exception) {
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>
);
}

function SpanEventError({
spanEvent,
exception,
}: {
spanEvent: OtelSpanEvent;
exception: OtelExceptionProperty;
}) {
return (
<div className="flex flex-col gap-2 rounded-sm border border-rose-500/50 p-3">
<SpanEventHeader title={"Error"} time={spanEvent.time} titleClassName="text-rose-500" />
{exception.message && <Callout variant="error">{exception.message}</Callout>}
{exception.stacktrace && <CodeBlock code={exception.stacktrace} maxLines={20} />}
</div>
);
}
129 changes: 129 additions & 0 deletions apps/webapp/app/components/runs/v3/SpanTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { ChevronRightIcon } from "@heroicons/react/20/solid";
import { Span } from "@opentelemetry/sdk-trace-base";
import { TaskEventStyle } from "@trigger.dev/core/v3";
import { TaskEventLevel } from "@trigger.dev/database";
import { Fragment } from "react";
import { Paragraph } from "~/components/primitives/Paragraph";
import { cn } from "~/utils/cn";

type SpanTitleProps = {
message: string;
isError: boolean;
style: TaskEventStyle;
level: TaskEventLevel;
size: "small" | "large";
};

export function SpanTitle(event: SpanTitleProps) {
return (
<span className={cn("inline-flex items-center gap-2", eventTextClassName(event))}>
{event.message} <SpanAccessory accessory={event.style.accessory} size={event.size} />
</span>
);
}

function SpanAccessory({
accessory,
size,
}: {
accessory: TaskEventStyle["accessory"];
size: SpanTitleProps["size"];
}) {
if (!accessory) {
return null;
}

switch (accessory.style) {
case "codepath": {
return (
<SpanCodePathAccessory
accessory={accessory}
className={cn(size === "large" ? "text-sm" : "text-xs")}
/>
);
}
default: {
return (
<div className={cn("flex gap-1")}>
{accessory.items.map((item, index) => (
<span key={index} className={cn("inline-flex items-center gap-1")}>
{item.text}
</span>
))}
</div>
);
}
}
}

export function SpanCodePathAccessory({
accessory,
className,
}: {
accessory: NonNullable<TaskEventStyle["accessory"]>;
className?: string;
}) {
return (
<code
className={cn(
"inline-flex items-center gap-0.5 rounded border border-slate-800 bg-midnight-850 px-1.5 py-0.5 font-mono text-sky-200",
className
)}
>
{accessory.items.map((item, index) => (
<Fragment key={index}>
<span
className={cn(
"inline-flex items-center",
index === accessory.items.length - 1 ? "text-yellow-200" : "text-dimmed"
)}
>
{item.text}
</span>
{index < accessory.items.length - 1 && (
<span className="text-slate-500">
<ChevronRightIcon className="h-4 w-4" />
</span>
)}
</Fragment>
))}
</code>
);
}

function eventTextClassName(event: SpanTitleProps) {
if (event.isError) {
return "text-rose-500";
}

switch (event.level) {
case "TRACE": {
return classNameForVariant(event.style.variant);
}
case "LOG":
case "INFO":
case "DEBUG": {
return classNameForVariant(event.style.variant);
}
case "WARN": {
return "text-amber-400";
}
case "ERROR": {
return "text-rose-500";
}
default: {
return classNameForVariant(event.style.variant);
}
}
}

function classNameForVariant(variant: TaskEventStyle["variant"]) {
switch (variant) {
case "primary": {
return "text-blue-500";
}
default: {
return "text-dimmed";
}
}
}
18 changes: 18 additions & 0 deletions apps/webapp/app/components/runs/v3/TaskPath.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SpanCodePathAccessory } from "./SpanTitle";

type TaskPathProps = {
filePath: string;
functionName: string;
className?: string;
};

export function TaskPath({ filePath, functionName, className }: TaskPathProps) {
return (
<SpanCodePathAccessory
accessory={{
items: [{ text: filePath }, { text: functionName }],
}}
className={className}
/>
);
}
8 changes: 6 additions & 2 deletions apps/webapp/app/presenters/v3/RunPresenter.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TaskEventStyle } from "@trigger.dev/core/v3";
import { Attributes } from "@opentelemetry/api";
import { TaskEventStyle, unflattenAttributes } from "@trigger.dev/core/v3";
import { TaskEvent } from "@trigger.dev/database";
import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView";
import { PrismaClient, prisma } from "~/db.server";
Expand Down Expand Up @@ -76,12 +77,15 @@ export class RunPresenter {

const tree = createTreeFromFlatItems(
events.map((event) => {
const styleUnflattened = unflattenAttributes(event.style as Attributes);
const style = TaskEventStyle.parse(styleUnflattened);

return {
id: event.spanId,
parentId: event.parentId ?? undefined,
data: {
message: event.message,
style: TaskEventStyle.parse(event.style),
style,
duration: Number(event.duration),
isError: event.isError,
isPartial: event.isPartial,
Expand Down
Loading

0 comments on commit 07efef4

Please sign in to comment.