Skip to content

Warm start UI #1882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/webapp/app/assets/icons/TraceIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function TraceIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="13" height="6" rx="2" fill="currentColor" />
<rect x="9" y="9" width="13" height="6" rx="2" fill="currentColor" />
<rect x="2" y="16" width="13" height="6" rx="2" fill="currentColor" />
</svg>
);
}
2 changes: 1 addition & 1 deletion apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function WaitpointTokenIcon({ className }: { className?: string }) {
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.71193 3.50338C6.89005 3.1921 7.22123 3.00004 7.57987 3.00003L16.4201 3C16.7787 3 17.1099 3.19206 17.288 3.50334L21.8658 11.5034C22.0419 11.8111 22.0419 12.189 21.8658 12.4967L17.288 20.4967C17.1099 20.8079 16.7787 21 16.4201 21H7.57987C7.22123 21 6.89005 20.8079 6.71193 20.4967L2.1342 12.4967C1.95813 12.189 1.95813 11.8111 2.1342 11.5034L6.71193 3.50338ZM8.5 9.00011C8.5 8.44783 8.94771 8.00011 9.5 8.00011H10C10.5523 8.00011 11 8.44783 11 9.00011V15.0001C11 15.5524 10.5523 16.0001 10 16.0001H9.5C8.94771 16.0001 8.5 15.5524 8.5 15.0001V9.00011ZM14 8.00006C13.4477 8.00006 13 8.44777 13 9.00006V15.0001C13 15.5523 13.4477 16.0001 14 16.0001H14.5C15.0523 16.0001 15.5 15.5523 15.5 15.0001V9.00006C15.5 8.44777 15.0523 8.00006 14.5 8.00006H14Z"
d="M15.1715 2C15.702 1.99999 16.2107 2.21072 16.5858 2.5858L21.4142 7.41435C21.7893 7.78943 22 8.29813 22 8.82855V15.1717C22 15.7021 21.7893 16.2109 21.4142 16.5859L16.5858 21.4144C16.2107 21.7894 15.702 22.0001 15.1716 22.0001H8.82842C8.29798 22.0001 7.78927 21.7894 7.4142 21.4144L2.58578 16.5859C2.21071 16.2109 2 15.7022 2 15.1717V8.82856C2 8.29814 2.21071 7.78943 2.58578 7.41436L7.41422 2.58586C7.78929 2.21079 8.29799 2.00007 8.82842 2.00007L15.1715 2ZM8.49997 9.00007C8.49997 8.44779 8.94769 8.00007 9.49997 8.00007H9.99997C10.5523 8.00007 11 8.44779 11 9.00007V15.0001C11 15.5524 10.5523 16.0001 9.99997 16.0001H9.49997C8.94769 16.0001 8.49997 15.5524 8.49997 15.0001V9.00007ZM14 8.00007C13.4477 8.00007 13 8.44779 13 9.00007V15.0001C13 15.5524 13.4477 16.0001 14 16.0001H14.5C15.0523 16.0001 15.5 15.5524 15.5 15.0001V9.00007C15.5 8.44779 15.0523 8.00007 14.5 8.00007H14Z"
fill="currentColor"
/>
</svg>
Expand Down
26 changes: 26 additions & 0 deletions apps/webapp/app/assets/icons/WarmStartIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FireIcon } from "@heroicons/react/20/solid";
import { cn } from "~/utils/cn";

function ColdStartIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.0016 2C12.7127 2 13.2872 2.55859 13.2872 3.25V4.42578L13.8898 3.83984C14.2675 3.47266 14.8782 3.47266 15.2518 3.83984C15.6255 4.20703 15.6295 4.80078 15.2518 5.16406L13.2832 7.07812V9.82422L15.75 8.42578L16.4611 5.84375C16.5977 5.34375 17.1281 5.04688 17.6423 5.17969C18.1566 5.3125 18.4619 5.82813 18.3253 6.32813L18.1164 7.08203L19.0645 6.54297C19.6792 6.19531 20.4667 6.39844 20.8243 6.99219C21.1818 7.58594 20.9769 8.35547 20.3622 8.70313L19.3458 9.28125L20.2176 9.50781C20.7319 9.64062 21.0372 10.1563 20.9006 10.6563C20.764 11.1563 20.2337 11.4531 19.7194 11.3203L16.9995 10.6133L14.5528 12L16.9995 13.3867L19.7194 12.6797C20.2337 12.5469 20.764 12.8437 20.9006 13.3437C21.0372 13.8437 20.7319 14.3594 20.2176 14.4922L19.3458 14.7188L20.3622 15.2969C20.9769 15.6445 21.1818 16.4102 20.8243 17.0078C20.4667 17.6055 19.6792 17.8047 19.0645 17.457L18.1164 16.918L18.3253 17.6719C18.4619 18.1719 18.1566 18.6875 17.6423 18.8203C17.1281 18.9531 16.5977 18.6563 16.4611 18.1563L15.75 15.5742L13.2872 14.1758V16.9219L15.2558 18.8359C15.6335 19.2031 15.6335 19.7969 15.2558 20.1602C14.8782 20.5234 14.2675 20.5273 13.8939 20.1602L13.2912 19.5742V20.75C13.2912 21.4414 12.7167 22 12.0056 22C11.2945 22 10.7199 21.4414 10.7199 20.75V19.5742L10.1173 20.1602C9.73964 20.5273 9.12896 20.5273 8.75532 20.1602C8.38169 19.793 8.37767 19.1992 8.75532 18.8359L10.724 16.9219V14.1758L8.25714 15.5742L7.54602 18.1563C7.40942 18.6563 6.87909 18.9531 6.36484 18.8203C5.85058 18.6875 5.54524 18.1719 5.68184 17.6719L5.89076 16.918L4.93456 17.4531C4.31987 17.8008 3.53241 17.5977 3.17485 17.0039C2.81728 16.4102 3.02619 15.6406 3.63687 15.293L4.65333 14.7148L3.78151 14.4883C3.26725 14.3555 2.96191 13.8398 3.09851 13.3398C3.23511 12.8398 3.76544 12.543 4.27969 12.6758L6.99962 13.3828L9.45037 12L7.00364 10.6133L4.28371 11.3203C3.76945 11.4531 3.23913 11.1563 3.10253 10.6563C2.96593 10.1563 3.27127 9.64062 3.78552 9.50781L4.65735 9.28125L3.64089 8.70313C3.02619 8.35547 2.82129 7.58984 3.17886 6.99609C3.53643 6.40234 4.32389 6.19922 4.93858 6.54688L5.88674 7.08594L5.67783 6.33203C5.54123 5.83203 5.84657 5.31641 6.36082 5.18359C6.87508 5.05078 7.4054 5.34766 7.542 5.84766L8.25312 8.42969L10.7159 9.82422V7.07812L8.74729 5.16406C8.36963 4.79688 8.36963 4.20312 8.74729 3.83984C9.12495 3.47656 9.73562 3.47266 10.1093 3.83984L10.7119 4.42578L10.7159 3.25C10.7159 2.55859 11.2904 2 12.0016 2Z"
fill="currentColor"
/>
</svg>
);
}

export function WarmStartIcon({
isWarmStart,
className,
}: {
isWarmStart: boolean;
className?: string;
}) {
if (isWarmStart) {
return <FireIcon className={cn("text-orange-400", className)} />;
}
return <ColdStartIcon className={cn("text-blue-400", className)} />;
}
59 changes: 59 additions & 0 deletions apps/webapp/app/components/WarmStarts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { WarmStartIcon } from "~/assets/icons/WarmStartIcon";
import { InfoIconTooltip, SimpleTooltip } from "./primitives/Tooltip";
import { cn } from "~/utils/cn";
import { Paragraph } from "./primitives/Paragraph";

export function WarmStartCombo({
isWarmStart,
showTooltip = false,
className,
}: {
isWarmStart: boolean;
showTooltip?: boolean;
className?: string;
}) {
return (
<div className={cn("flex items-center gap-1 text-sm text-text-dimmed", className)}>
<WarmStartIcon isWarmStart={isWarmStart} className="size-5" />
<span>{isWarmStart ? "Warm Start" : "Cold Start"}</span>
{showTooltip && <InfoIconTooltip content={<WarmStartTooltipContent />} />}
</div>
);
}

export function WarmStartIconWithTooltip({
isWarmStart,
className,
}: {
isWarmStart: boolean;
className?: string;
}) {
return (
<SimpleTooltip
className="relative z-[9999]"
button={<WarmStartIcon isWarmStart={isWarmStart} className={className} />}
content={<WarmStartTooltipContent />}
/>
);
}

function WarmStartTooltipContent() {
return (
<div className="flex max-w-xs flex-col gap-4 p-1">
<div>
<WarmStartCombo isWarmStart={false} className="mb-0.5 text-text-bright" />
<Paragraph variant="small" className="!text-wrap text-text-dimmed">
A cold start happens when we need to boot up a new machine for your run to execute. This
takes longer than a warm start.
</Paragraph>
</div>
<div>
<WarmStartCombo isWarmStart={true} className="mb-0.5 text-text-bright" />
<Paragraph variant="small" className="!text-wrap text-text-dimmed">
A warm start happens when we can reuse a machine from a run that recently finished. This
takes less time than a cold start.
</Paragraph>
</div>
</div>
);
}
22 changes: 12 additions & 10 deletions apps/webapp/app/components/primitives/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
TooltipContentProps
>(({ className, sideOffset = 4, variant = "basic", ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 focus-visible:outline-none",
variantClasses[variant],
className
)}
{...props}
/>
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 focus-visible:outline-none",
variantClasses[variant],
className
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

Expand Down
3 changes: 2 additions & 1 deletion apps/webapp/app/components/runs/v3/RunIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MiddlewareIcon } from "~/assets/icons/MiddlewareIcon";
import { FunctionIcon } from "~/assets/icons/FunctionIcon";
import { TriggerIcon } from "~/assets/icons/TriggerIcon";
import { PythonLogoIcon } from "~/assets/icons/PythonLogoIcon";
import { TraceIcon } from "~/assets/icons/TraceIcon";

type TaskIconProps = {
name: string | undefined;
Expand Down Expand Up @@ -65,7 +66,7 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
case "wait":
return <PauseIcon className={cn(className, "text-teal-500")} />;
case "trace":
return <Squares2X2Icon className={cn(className, "text-text-dimmed")} />;
return <TraceIcon className={cn(className, "text-text-dimmed")} />;
case "tag":
return <TagIcon className={cn(className, "text-text-dimmed")} />;
case "queue":
Expand Down
21 changes: 19 additions & 2 deletions apps/webapp/app/presenters/v3/SpanPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import {
isWaitpointOutputTimeout,
type MachinePresetName,
prettyPrintPacket,
SemanticInternalAttributes,
TaskRunError,
} from "@trigger.dev/core/v3";
import { getMaxDuration } from "@trigger.dev/core/v3/isomorphic";
import { RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { logger } from "~/services/logger.server";
import { eventRepository } from "~/v3/eventRepository.server";
import { eventRepository, rehydrateAttribute } from "~/v3/eventRepository.server";
import { machinePresetFromName } from "~/v3/machinePresets.server";
import { getTaskEventStoreTableForRun, type TaskEventStoreTable } from "~/v3/taskEventStore.server";
import { isFailedRunStatus, isFinalRunStatus } from "~/v3/taskStatus";
Expand Down Expand Up @@ -455,7 +456,7 @@ export class SpanPresenter extends BasePresenter {
};

switch (span.entity.type) {
case "waitpoint":
case "waitpoint": {
if (!span.entity.id) {
logger.error(`SpanPresenter: No waitpoint id`, {
spanId,
Expand Down Expand Up @@ -486,7 +487,23 @@ export class SpanPresenter extends BasePresenter {
object: waitpoint,
},
};
}
case "attempt": {
const isWarmStart = rehydrateAttribute<boolean>(
span.metadata,
SemanticInternalAttributes.WARM_START
);

return {
...data,
entity: {
type: "attempt" as const,
object: {
isWarmStart,
},
},
};
}
default:
return { ...data, entity: null };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,18 @@ import {
tryCatch,
} from "@trigger.dev/core/v3";
import { type RuntimeEnvironmentType } from "@trigger.dev/database";
import { AnimatePresence, motion } from "framer-motion";
import { motion } from "framer-motion";
import { useCallback, useEffect, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { DisconnectedIcon } from "~/assets/icons/ConnectionIcons";
import { redirect } from "remix-typedjson";
import { ShowParentIcon, ShowParentIconSelected } from "~/assets/icons/ShowParentIcon";
import tileBgPath from "~/assets/images/error-banner-tile@2x.png";
import {
DevDisconnectedBanner,
useCrossEngineIsConnected,
useDevPresence,
} from "~/components/DevPresence";
import { DevDisconnectedBanner, useCrossEngineIsConnected } from "~/components/DevPresence";
import { WarmStartIconWithTooltip } from "~/components/WarmStarts";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { PageBody } from "~/components/layout/AppLayout";
import { Badge } from "~/components/primitives/Badge";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { ClipboardField } from "~/components/primitives/ClipboardField";
import { DateTimeShort } from "~/components/primitives/DateTime";
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { Header3 } from "~/components/primitives/Headers";
Expand Down Expand Up @@ -100,8 +95,6 @@ import {
} from "~/utils/pathBuilder";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
import { SpanView } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route";
import { redirectWithErrorMessage } from "~/models/message.server";
import { redirect } from "remix-typedjson";

const resizableSettings = {
parent: {
Expand Down Expand Up @@ -1045,7 +1038,13 @@ function NodeText({ node }: { node: TraceEvent }) {

function NodeStatusIcon({ node }: { node: TraceEvent }) {
if (node.data.level !== "TRACE") return null;
if (node.data.style.variant !== "primary") return null;
if (!node.data.style.variant) return null;

if (node.data.style.variant === "warm") {
return <WarmStartIconWithTooltip isWarmStart={true} className="size-4" />;
} else if (node.data.style.variant === "cold") {
return <WarmStartIconWithTooltip isWarmStart={false} className="size-4" />;
}

if (node.data.isCancelled) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {
EnvelopeIcon,
QueueListIcon,
} from "@heroicons/react/20/solid";
import { Link } from "@remix-run/react";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import {
formatDurationMilliseconds,
type TaskRunError,
taskRunErrorEnhancer,
} from "@trigger.dev/core/v3";
import { assertNever } from "assert-never";
import { useEffect } from "react";
import { typedjson, useTypedFetcher } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
Expand All @@ -37,20 +37,23 @@ import { TabButton, TabContainer } from "~/components/primitives/Tabs";
import { TextLink } from "~/components/primitives/TextLink";
import { InfoIconTooltip, SimpleTooltip } from "~/components/primitives/Tooltip";
import { RunTimeline, RunTimelineEvent, SpanTimeline } from "~/components/run/RunTimeline";
import { PacketDisplay } from "~/components/runs/v3/PacketDisplay";
import { RunIcon } from "~/components/runs/v3/RunIcon";
import { RunTag } from "~/components/runs/v3/RunTag";
import { SpanEvents } from "~/components/runs/v3/SpanEvents";
import { SpanTitle } from "~/components/runs/v3/SpanTitle";
import { TaskRunAttemptStatusCombo } from "~/components/runs/v3/TaskRunAttemptStatus";
import { TaskRunStatusCombo, TaskRunStatusReason } from "~/components/runs/v3/TaskRunStatus";
import { WaitpointDetailTable } from "~/components/runs/v3/WaitpointDetails";
import { WarmStartCombo } from "~/components/WarmStarts";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useSearchParams } from "~/hooks/useSearchParam";
import { useHasAdminAccess } from "~/hooks/useUser";
import { redirectWithErrorMessage } from "~/models/message.server";
import { type Span, SpanPresenter, type SpanRun } from "~/presenters/v3/SpanPresenter.server";
import { logger } from "~/services/logger.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import { formatCurrencyAccurate } from "~/utils/numberFormatter";
import {
Expand All @@ -63,15 +66,8 @@ import {
v3SchedulePath,
v3SpanParamsSchema,
} from "~/utils/pathBuilder";
import {
CompleteWaitpointForm,
ForceTimeout,
} from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route";
import { useEnvironment } from "~/hooks/useEnvironment";
import { WaitpointStatusCombo } from "~/components/runs/v3/WaitpointStatus";
import { PacketDisplay } from "~/components/runs/v3/PacketDisplay";
import { WaitpointDetailTable } from "~/components/runs/v3/WaitpointDetails";
import { createTimelineSpanEventsFromSpanEvents } from "~/utils/timelineSpanEvents";
import { CompleteWaitpointForm } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route";

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { projectParam, organizationSlug, envParam, runParam, spanParam } =
Expand Down Expand Up @@ -935,6 +931,40 @@ function SpanEntity({ span }: { span: Span }) {
}

switch (span.entity.type) {
case "attempt": {
return (
<div className="flex flex-col gap-4 p-3">
<div className="border-b border-grid-bright pb-3">
<TaskRunAttemptStatusCombo
status={
span.isCancelled
? "CANCELED"
: span.isError
? "FAILED"
: span.isPartial
? "EXECUTING"
: "COMPLETED"
}
className="text-sm"
/>
</div>
<SpanTimeline
startTime={new Date(span.startTime)}
duration={span.duration}
inProgress={span.isPartial}
isError={span.isError}
events={createTimelineSpanEventsFromSpanEvents(span.events, isAdmin)}
/>
{span.entity.object.isWarmStart !== undefined ? (
<WarmStartCombo
isWarmStart={span.entity.object.isWarmStart}
showTooltip
className="mt-3"
/>
) : null}
</div>
);
}
case "waitpoint": {
return (
<div className="grid h-full grid-rows-[1fr_auto]">
Expand All @@ -957,7 +987,7 @@ function SpanEntity({ span }: { span: Span }) {
);
}
default: {
return <Paragraph variant="small">No span for {span.entity.type}</Paragraph>;
assertNever(span.entity);
}
}
}
6 changes: 4 additions & 2 deletions apps/webapp/app/v3/eventRepository.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1638,7 +1638,7 @@ function rehydrateShow(properties: Prisma.JsonValue): { actions?: boolean } | un
return;
}

function rehydrateAttribute<T extends AttributeValue>(
export function rehydrateAttribute<T extends AttributeValue>(
properties: Prisma.JsonValue,
key: string
): T | undefined {
Expand All @@ -1656,7 +1656,9 @@ function rehydrateAttribute<T extends AttributeValue>(

const value = properties[key];

if (!value) return;
if (value === undefined) {
return;
}

return value as T;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/v3/logger/taskLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class OtelTaskLogger implements TaskLogger {
...options,
attributes: {
...options?.attributes,
...(options?.icon ? { [SemanticInternalAttributes.STYLE_ICON]: options.icon } : {}),
[SemanticInternalAttributes.STYLE_ICON]: options?.icon ?? "trace",
},
};

Expand Down
Loading