From aa037a289be60e686cb64fce1399502fd2430137 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 11:56:57 +0100 Subject: [PATCH 01/10] Better trace icon --- apps/webapp/app/assets/icons/TraceIcon.tsx | 9 +++++++++ apps/webapp/app/components/runs/v3/RunIcon.tsx | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/app/assets/icons/TraceIcon.tsx diff --git a/apps/webapp/app/assets/icons/TraceIcon.tsx b/apps/webapp/app/assets/icons/TraceIcon.tsx new file mode 100644 index 0000000000..20eb107848 --- /dev/null +++ b/apps/webapp/app/assets/icons/TraceIcon.tsx @@ -0,0 +1,9 @@ +export function TraceIcon({ className }: { className?: string }) { + return ( + + + + + + ); +} diff --git a/apps/webapp/app/components/runs/v3/RunIcon.tsx b/apps/webapp/app/components/runs/v3/RunIcon.tsx index 903276a539..c03b32731d 100644 --- a/apps/webapp/app/components/runs/v3/RunIcon.tsx +++ b/apps/webapp/app/components/runs/v3/RunIcon.tsx @@ -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; @@ -65,7 +66,7 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) { case "wait": return ; case "trace": - return ; + return ; case "tag": return ; case "queue": From a1463b30bfabceb311fb602619ff45b5d0410911 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 11:57:14 +0100 Subject: [PATCH 02/10] Better Waitpoint token icon --- apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx b/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx index 34ba9438c8..87b7ee1217 100644 --- a/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx +++ b/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx @@ -2,9 +2,9 @@ export function WaitpointTokenIcon({ className }: { className?: string }) { return ( From ea25c0796c86ba199c6103ec0b5cddbb876efb33 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 13:31:34 +0100 Subject: [PATCH 03/10] Fix for bad jsx --- apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx b/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx index 87b7ee1217..23269fb8f0 100644 --- a/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx +++ b/apps/webapp/app/assets/icons/WaitpointTokenIcon.tsx @@ -2,8 +2,8 @@ export function WaitpointTokenIcon({ className }: { className?: string }) { return ( From a9ec999a111307562ff36ce5c76eb68e3ddd8d68 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 13:31:42 +0100 Subject: [PATCH 04/10] Warm and cold start icons --- .../webapp/app/assets/icons/WarmStartIcon.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 apps/webapp/app/assets/icons/WarmStartIcon.tsx diff --git a/apps/webapp/app/assets/icons/WarmStartIcon.tsx b/apps/webapp/app/assets/icons/WarmStartIcon.tsx new file mode 100644 index 0000000000..211b27a98f --- /dev/null +++ b/apps/webapp/app/assets/icons/WarmStartIcon.tsx @@ -0,0 +1,26 @@ +import { FireIcon } from "@heroicons/react/20/solid"; +import { cn } from "~/utils/cn"; + +function ColdStartIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export function WarmStartIcon({ + isWarmStart, + className, +}: { + isWarmStart: boolean; + className?: string; +}) { + if (isWarmStart) { + return ; + } + return ; +} From 02ae9f4574dc30ff7874c66c34323fc0b0b34772 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 13:31:54 +0100 Subject: [PATCH 05/10] Tooltips now use a so they appear on top --- .../app/components/primitives/Tooltip.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/webapp/app/components/primitives/Tooltip.tsx b/apps/webapp/app/components/primitives/Tooltip.tsx index 80b1427cad..53a5f4959c 100644 --- a/apps/webapp/app/components/primitives/Tooltip.tsx +++ b/apps/webapp/app/components/primitives/Tooltip.tsx @@ -37,16 +37,18 @@ const TooltipContent = React.forwardRef< React.ElementRef, TooltipContentProps >(({ className, sideOffset = 4, variant = "basic", ...props }, ref) => ( - + + + )); TooltipContent.displayName = TooltipPrimitive.Content.displayName; From 285ad9a632739a3865dd1354c41cb568a35af68d Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 13:32:03 +0100 Subject: [PATCH 06/10] Warm start components --- apps/webapp/app/components/WarmStarts.tsx | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 apps/webapp/app/components/WarmStarts.tsx diff --git a/apps/webapp/app/components/WarmStarts.tsx b/apps/webapp/app/components/WarmStarts.tsx new file mode 100644 index 0000000000..07a894b9e7 --- /dev/null +++ b/apps/webapp/app/components/WarmStarts.tsx @@ -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 ( +
+ + {isWarmStart ? "Warm Start" : "Cold Start"} + {showTooltip && } />} +
+ ); +} + +export function WarmStartIconWithTooltip({ + isWarmStart, + className, +}: { + isWarmStart: boolean; + className?: string; +}) { + return ( + } + content={} + /> + ); +} + +function WarmStartTooltipContent() { + return ( +
+
+ + + 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. + +
+
+ + + A warm start happens when we can reuse a machine from a run that recently finished. This + takes less time than a cold start. + +
+
+ ); +} From f5317b705038d0f1bd4df0fd6db6705b3f57bf25 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 13:33:05 +0100 Subject: [PATCH 07/10] Added warm start markers to the Run page and inspector --- .../app/presenters/v3/SpanPresenter.server.ts | 25 ++++++++- .../route.tsx | 23 ++++---- .../route.tsx | 52 +++++++++++++++---- 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 3c09282c04..d7caf257f4 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -2,6 +2,7 @@ import { isWaitpointOutputTimeout, type MachinePresetName, prettyPrintPacket, + SemanticInternalAttributes, TaskRunError, } from "@trigger.dev/core/v3"; import { getMaxDuration } from "@trigger.dev/core/v3/isomorphic"; @@ -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, @@ -486,9 +487,29 @@ export class SpanPresenter extends BasePresenter { object: waitpoint, }, }; - + } + case "attempt": { + return { + ...data, + entity: { + type: "attempt" as const, + object: { + isWarmStart: isWarmStart(span.properties), + }, + }, + }; + } default: return { ...data, entity: null }; } } } + +function isWarmStart( + attributes: string | number | boolean | Record | null | undefined +): boolean | undefined { + if (!attributes || typeof attributes !== "object") return undefined; + const attribute = attributes[SemanticInternalAttributes.WARM_START]; + if (typeof attribute !== "boolean") return undefined; + return attribute; +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 00ef7dd2ad..24ee6e6123 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -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"; @@ -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: { @@ -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 ; + } else if (node.data.style.variant === "cold") { + return ; + } if (node.data.isCancelled) { return ( diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx index e11a93f6d2..b6ad7ec01f 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx @@ -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"; @@ -37,12 +37,16 @@ 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"; @@ -50,7 +54,6 @@ 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 { @@ -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 } = @@ -935,6 +931,40 @@ function SpanEntity({ span }: { span: Span }) { } switch (span.entity.type) { + case "attempt": { + return ( +
+
+ +
+ + {span.entity.object.isWarmStart !== undefined ? ( + + ) : null} +
+ ); + } case "waitpoint": { return (
@@ -957,7 +987,7 @@ function SpanEntity({ span }: { span: Span }) { ); } default: { - return No span for {span.entity.type}; + assertNever(span.entity); } } } From 036a41430bfa4d9e2dd03ac4a52c3ded7c9c8606 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 13:59:01 +0100 Subject: [PATCH 08/10] Fix for getting the correct value from the metadata --- apps/webapp/app/presenters/v3/SpanPresenter.server.ts | 9 +++++++-- apps/webapp/app/v3/eventRepository.server.ts | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index d7caf257f4..b612ce5eec 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -8,7 +8,7 @@ import { 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"; @@ -489,12 +489,17 @@ export class SpanPresenter extends BasePresenter { }; } case "attempt": { + const isWarmStart = rehydrateAttribute( + span.metadata, + SemanticInternalAttributes.WARM_START + ); + return { ...data, entity: { type: "attempt" as const, object: { - isWarmStart: isWarmStart(span.properties), + isWarmStart, }, }, }; diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index 990c6127ef..38a515925a 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -1638,7 +1638,7 @@ function rehydrateShow(properties: Prisma.JsonValue): { actions?: boolean } | un return; } -function rehydrateAttribute( +export function rehydrateAttribute( properties: Prisma.JsonValue, key: string ): T | undefined { @@ -1656,7 +1656,9 @@ function rehydrateAttribute( const value = properties[key]; - if (!value) return; + if (value === undefined) { + return; + } return value as T; } From 066e16688459c5e7fd239f5d964f1969dbeff5dd Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 14:12:56 +0100 Subject: [PATCH 09/10] Better trace icon, with fallback to a passed in one --- packages/core/src/v3/logger/taskLogger.ts | 2 +- references/hello-world/src/trigger/example.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/core/src/v3/logger/taskLogger.ts b/packages/core/src/v3/logger/taskLogger.ts index 8fbfd19cc6..aed1c1a7fb 100644 --- a/packages/core/src/v3/logger/taskLogger.ts +++ b/packages/core/src/v3/logger/taskLogger.ts @@ -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", }, }; diff --git a/references/hello-world/src/trigger/example.ts b/references/hello-world/src/trigger/example.ts index d1b008f417..b72fa03fa9 100644 --- a/references/hello-world/src/trigger/example.ts +++ b/references/hello-world/src/trigger/example.ts @@ -12,6 +12,20 @@ export const helloWorldTask = task({ logger.warn("warn: Hello, world!", { payload }); logger.error("error: Hello, world!", { payload }); + logger.trace("my trace", async (span) => { + logger.debug("some log", { span }); + }); + + logger.trace( + "my trace", + async (span) => { + logger.debug("some log", { span }); + }, + { + icon: "tabler-ad-circle", + } + ); + await wait.for({ seconds: 5 }); return { From 71efd15c4f6fa47bab58758f0af75f34344d6b93 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 4 Apr 2025 14:18:19 +0100 Subject: [PATCH 10/10] Removed unused isWarmStart function --- apps/webapp/app/presenters/v3/SpanPresenter.server.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index b612ce5eec..00f2846072 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -509,12 +509,3 @@ export class SpanPresenter extends BasePresenter { } } } - -function isWarmStart( - attributes: string | number | boolean | Record | null | undefined -): boolean | undefined { - if (!attributes || typeof attributes !== "object") return undefined; - const attribute = attributes[SemanticInternalAttributes.WARM_START]; - if (typeof attribute !== "boolean") return undefined; - return attribute; -}