From b838c3b0824fa09b06ebe39f5871c6f7775a8477 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:18:17 +0100 Subject: [PATCH 1/3] feat(webapp): surface run cell in the run admin panel --- .../app/presenters/v3/SpanPresenter.server.ts | 34 +++++++++++++++++++ .../route.tsx | 14 ++++++-- .../v3/mollifier/syntheticSpanRun.server.ts | 1 + 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index b680a1c6a8e..5eeb5570ffb 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -106,6 +106,7 @@ export class SpanPresenter extends BasePresenter { spanId, runFriendlyId, linkedRunId, + isAdmin, }: { userId: string; projectSlug: string; @@ -117,6 +118,7 @@ export class SpanPresenter extends BasePresenter { spanId: string; runFriendlyId: string; linkedRunId?: string; + isAdmin?: boolean; }) { const project = await this._replica.project.findFirst({ where: { @@ -197,6 +199,7 @@ export class SpanPresenter extends BasePresenter { eventRepository: repository, spanId, linkedRunId, + isAdmin, createdAt: parentRun.createdAt, completedAt: parentRun.completedAt, environmentId: parentRun.runtimeEnvironmentId, @@ -236,6 +239,7 @@ export class SpanPresenter extends BasePresenter { eventRepository, spanId, linkedRunId, + isAdmin, createdAt, completedAt, }: { @@ -245,6 +249,7 @@ export class SpanPresenter extends BasePresenter { eventRepository: IEventRepository; spanId: string; linkedRunId?: string; + isAdmin?: boolean; createdAt: Date; completedAt: Date | null; }) { @@ -382,6 +387,34 @@ export class SpanPresenter extends BasePresenter { } : undefined; + // Cell attribution is telemetry-only - a `trigger.cell` resource attribute + // on the run's span, never a DB field. Admin-only + best-effort: the span + // may not be in the event store yet (queued/buffered/ingestion lag) and the + // store may be unreachable, so any failure degrades to undefined rather than + // breaking the panel. + let cell: string | undefined; + if (isAdmin) { + try { + const rootSpan = await eventRepository.getSpan( + eventStore, + environmentId, + run.spanId, + traceId, + createdAt, + completedAt ?? undefined + ); + const resource = rootSpan?.resourceProperties as Record | undefined; + const value = resource?.trigger?.cell ?? resource?.["trigger.cell"]; + cell = typeof value === "string" ? value : undefined; + } catch (error) { + logger.warn("Failed to resolve run cell from telemetry", { + runId: run.id, + spanId: run.spanId, + error: error instanceof Error ? error.message : String(error), + }); + } + } + return { id: run.id, friendlyId: run.friendlyId, @@ -453,6 +486,7 @@ export class SpanPresenter extends BasePresenter { isBuffered: false, machinePreset: machine?.name, taskEventStore: run.taskEventStore, + cell, externalTraceId, }; } 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 4c8f77a582a..9859d8299d4 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 @@ -84,7 +84,7 @@ 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 { requireUser } from "~/services/session.server"; import { cn } from "~/utils/cn"; import { formatCurrencyAccurate } from "~/utils/numberFormatter"; import { @@ -108,7 +108,10 @@ import { RealtimeStreamViewer } from "../resources.orgs.$organizationSlug.projec import { CompleteWaitpointForm } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { - const userId = await requireUserId(request); + const user = await requireUser(request); + // Admin gate for the telemetry-backed "Cell" field — only admins trigger the + // extra event-store read (below), and only admins see the panel. + const isAdmin = user.admin || user.isImpersonating; const { projectParam, organizationSlug, envParam, runParam, spanParam } = v3SpanParamsSchema.parse(params); @@ -123,7 +126,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { envSlug: envParam, spanId: spanParam, runFriendlyId: runParam, - userId, + userId: user.id, + isAdmin, linkedRunId, }); @@ -1044,6 +1048,10 @@ function RunBody({ Task event store {run.taskEventStore} + + Cell + {run.cell ?? "-"} + )} diff --git a/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts b/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts index f36d5a5d28e..4e665a1c860 100644 --- a/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts +++ b/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts @@ -198,6 +198,7 @@ export async function buildSyntheticSpanRun(args: { isBuffered: true, machinePreset: narrowMachinePreset(run.machinePreset), taskEventStore: "taskEvent", + cell: undefined, externalTraceId: undefined, }; } From aafa80740291a02e9730620a3d30e1c11ff2b93d Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:58:01 +0100 Subject: [PATCH 2/3] fix(webapp): resolve run cell lookup for cached spans + align placeholder --- apps/webapp/app/presenters/v3/SpanPresenter.server.ts | 9 ++++++--- .../route.tsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 5eeb5570ffb..2f6dbe8a83b 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -395,13 +395,16 @@ export class SpanPresenter extends BasePresenter { let cell: string | undefined; if (isAdmin) { try { + // Use the resolved run's own trace + time window (not the parent's) so + // the lookup is correct for cached/linked spans, where `run` is the + // original run rather than `parentRun`. const rootSpan = await eventRepository.getSpan( eventStore, environmentId, run.spanId, - traceId, - createdAt, - completedAt ?? undefined + run.traceId, + run.createdAt, + run.completedAt ?? undefined ); const resource = rootSpan?.resourceProperties as Record | undefined; const value = resource?.trigger?.cell ?? resource?.["trigger.cell"]; 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 9859d8299d4..50b5ada1e36 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 @@ -1050,7 +1050,7 @@ function RunBody({ Cell - {run.cell ?? "-"} + {run.cell ?? "–"} )} From 37267ea41fa66ba6035ad57e5a272b3203dc6269 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:29:32 +0100 Subject: [PATCH 3/3] fix(webapp): resolve cell lookup event store from the original run --- .../app/presenters/v3/SpanPresenter.server.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 2f6dbe8a83b..52097e83278 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -395,11 +395,16 @@ export class SpanPresenter extends BasePresenter { let cell: string | undefined; if (isAdmin) { try { - // Use the resolved run's own trace + time window (not the parent's) so - // the lookup is correct for cached/linked spans, where `run` is the - // original run rather than `parentRun`. - const rootSpan = await eventRepository.getSpan( - eventStore, + // Resolve the store + repository from the resolved run itself (not the + // parent) so cached/linked spans - where `run` is the original run and + // may live in a different event store - resolve correctly. + const runEventStore = getTaskEventStoreTableForRun(run); + const runEventRepository = await getEventRepositoryForStore( + run.taskEventStore, + environment.organization.id + ); + const rootSpan = await runEventRepository.getSpan( + runEventStore, environmentId, run.spanId, run.traceId,