Skip to content

Commit a90a495

Browse files
authored
feat(webapp,database): show a Test column for agent sessions (#4011)
## Summary Sessions started from the agent Test playground were tagged with a `"playground"` tag that rendered in the Sessions table's Tags column. They are now flagged with a real `Session.isTest` boolean (mirroring `TaskRun.isTest`) and surfaced as a dedicated **Test** column with a check icon, to the left of Tags, on both the Sessions page and the Agent landing page, plus a matching **Test** property on the session detail page. This mirrors how Standard and Scheduled task runs already indicate test runs. ## Design `isTest` is a new `Session` column (Postgres) replicated into ClickHouse `sessions_v1` alongside the existing fields. The Sessions list reads `isTest` from Postgres for display (ClickHouse only supplies the ordered session IDs), so the column renders correctly without a ClickHouse backfill. The playground action now sets `isTest: true` on session create instead of writing the `"playground"` tag. The triggered run still carries `playground:true` in its own tags (unchanged). A migration backfills existing sessions, setting `isTest = true` and stripping the now-redundant `"playground"` tag where it is present, so the list and detail views render consistently without read-time tag filtering.
1 parent 7efdbc8 commit a90a495

14 files changed

Lines changed: 90 additions & 6 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Agent sessions started from the Test playground are now flagged with a real `Session.isTest` boolean instead of a `"playground"` tag, surfaced as a dedicated "Test" column (check icon) in the Sessions table on both the Sessions and Agent pages, plus a matching property on the session detail page. The legacy `"playground"` tag is hidden from the Tags display on pre-existing sessions.

apps/webapp/app/components/sessions/v1/SessionsTable.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ArrowRightIcon } from "@heroicons/react/20/solid";
2+
import { CheckIcon } from "@heroicons/react/24/solid";
23
import { useLocation, useNavigation } from "@remix-run/react";
34
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
45
import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon";
@@ -87,6 +88,7 @@ export function SessionsTable({
8788
</TableHeaderCell>
8889
<TableHeaderCell>Type</TableHeaderCell>
8990
<TableHeaderCell>Agent ID</TableHeaderCell>
91+
<TableHeaderCell>Test</TableHeaderCell>
9092
<TableHeaderCell>Tags</TableHeaderCell>
9193
<TableHeaderCell>Created</TableHeaderCell>
9294
<TableHeaderCell>Duration</TableHeaderCell>
@@ -97,7 +99,7 @@ export function SessionsTable({
9799
</TableHeader>
98100
<TableBody>
99101
{sessions.length === 0 ? (
100-
<TableBlankRow colSpan={8}>
102+
<TableBlankRow colSpan={9}>
101103
<div className="flex items-center justify-center">
102104
<Paragraph className="w-auto">
103105
{hasFilters
@@ -144,6 +146,19 @@ export function SessionsTable({
144146
<MiddleTruncate text={session.taskIdentifier} className="font-mono text-xs" />
145147
</div>
146148
</TableCell>
149+
<TableCell to={sessionPath}>
150+
<span className="sr-only">{session.isTest ? "Yes" : "No"}</span>
151+
{session.isTest ? (
152+
<CheckIcon
153+
aria-hidden
154+
className="size-4 text-charcoal-400 group-hover/table-row:text-text-bright"
155+
/>
156+
) : (
157+
<span aria-hidden className="text-text-dimmed">
158+
159+
</span>
160+
)}
161+
</TableCell>
147162
<TableCell to={sessionPath}>
148163
{session.tags.length > 0 ? (
149164
<div className="flex flex-wrap gap-1">
@@ -168,7 +183,7 @@ export function SessionsTable({
168183
)}
169184
{isLoading && (
170185
<TableBlankRow
171-
colSpan={8}
186+
colSpan={9}
172187
className="absolute left-0 top-0 flex h-full w-full items-center justify-center gap-2 bg-charcoal-900/90"
173188
>
174189
<Spinner /> <span className="text-text-dimmed">Loading…</span>

apps/webapp/app/presenters/v3/SessionListPresenter.server.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server";
77
import {
88
type SessionStatus,
99
SessionsRepository,
10+
LEGACY_PLAYGROUND_TAG,
1011
} from "~/services/sessionsRepository/sessionsRepository.server";
1112
import { ServiceValidationError } from "~/v3/services/baseService.server";
1213
import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server";
@@ -225,7 +226,13 @@ export class SessionListPresenter {
225226
externalId: session.externalId,
226227
type: session.type,
227228
taskIdentifier: session.taskIdentifier,
228-
tags: session.tags ? [...session.tags].sort((a, b) => a.localeCompare(b)) : [],
229+
isTest: session.isTest,
230+
// Hide the legacy "playground" tag (pre-isTest sessions) from display.
231+
tags: session.tags
232+
? [...session.tags]
233+
.filter((t) => t !== LEGACY_PLAYGROUND_TAG)
234+
.sort((a, b) => a.localeCompare(b))
235+
: [],
229236
status,
230237
closedAt: session.closedAt ? session.closedAt.toISOString() : undefined,
231238
closedReason: session.closedReason ?? undefined,

apps/webapp/app/presenters/v3/SessionPresenter.server.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { env } from "~/env.server";
44
import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server";
55
import { chatSnapshotStorageKey } from "~/services/realtime/chatSnapshot.server";
66
import { resolveSessionByIdOrExternalId } from "~/services/realtime/sessions.server";
7+
import { LEGACY_PLAYGROUND_TAG } from "~/services/sessionsRepository/sessionsRepository.server";
78
import { logger } from "~/services/logger.server";
89
import { generatePresignedUrl } from "~/v3/objectStore.server";
910
import { runStore } from "~/v3/runStore.server";
@@ -177,7 +178,13 @@ export class SessionPresenter {
177178
externalId: session.externalId,
178179
type: session.type,
179180
taskIdentifier: session.taskIdentifier,
180-
tags: session.tags ? [...session.tags].sort((a, b) => a.localeCompare(b)) : [],
181+
isTest: session.isTest,
182+
// Hide the legacy "playground" tag (pre-isTest sessions) from display.
183+
tags: session.tags
184+
? [...session.tags]
185+
.filter((t) => t !== LEGACY_PLAYGROUND_TAG)
186+
.sort((a, b) => a.localeCompare(b))
187+
: [],
181188
metadata: session.metadata,
182189
triggerConfig: session.triggerConfig,
183190
streamBasinName: session.streamBasinName,

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BoltIcon, BoltSlashIcon } from "@heroicons/react/20/solid";
2-
import { BookOpenIcon } from "@heroicons/react/24/solid";
2+
import { BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid";
33
import { type MetaFunction } from "@remix-run/react";
44
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
55
import { useVirtualizer } from "@tanstack/react-virtual";
@@ -818,6 +818,19 @@ function OverviewTab({ session, status }: { session: LoadedSession; status: Sess
818818
<span className="font-mono text-sm">{session.taskIdentifier}</span>
819819
</Property.Value>
820820
</Property.Item>
821+
<Property.Item>
822+
<Property.Label>Test</Property.Label>
823+
<Property.Value>
824+
<span className="sr-only">{session.isTest ? "Yes" : "No"}</span>
825+
{session.isTest ? (
826+
<CheckIcon aria-hidden className="size-4 text-text-dimmed" />
827+
) : (
828+
<span aria-hidden className="text-text-dimmed">
829+
830+
</span>
831+
)}
832+
</Property.Value>
833+
</Property.Item>
821834
{session.currentRun ? (
822835
<Property.Item>
823836
<Property.Label>Current run</Property.Label>

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
162162
type: "chat.agent",
163163
taskIdentifier: agentSlug,
164164
triggerConfig: triggerConfig as unknown as Prisma.InputJsonValue,
165-
tags: ["playground"],
165+
// Mark as a Test session — surfaced via the Test column in the
166+
// Sessions table. Session tags stay empty; the triggered run still
167+
// carries "playground:true" via triggerConfig.tags.
168+
isTest: true,
166169
projectId: project.id,
167170
runtimeEnvironmentId: environment.id,
168171
environmentType: environment.type,

apps/webapp/app/services/sessionsReplicationService.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ function toSessionInsertArray(
802802
session.expiresAt ? session.expiresAt.getTime() : null,
803803
session.createdAt.getTime(),
804804
session.updatedAt.getTime(),
805+
session.isTest ?? false,
805806
version.toString(),
806807
isDeleted ? 1 : 0,
807808
];

apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export class ClickHouseSessionsRepository implements ISessionsRepository {
9393
externalId: true,
9494
type: true,
9595
taskIdentifier: true,
96+
isTest: true,
9697
tags: true,
9798
metadata: true,
9899
closedAt: true,

apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export type SessionsRepositoryOptions = {
2424
export const SessionStatus = z.enum(["ACTIVE", "CLOSED", "EXPIRED"]);
2525
export type SessionStatus = z.infer<typeof SessionStatus>;
2626

27+
/**
28+
* Legacy marker tag for sessions created from the Test/playground before the
29+
* `Session.isTest` boolean existed. New sessions set `isTest` instead; this tag
30+
* is hidden from the Tags display so it doesn't surface on pre-isTest rows.
31+
*/
32+
export const LEGACY_PLAYGROUND_TAG = "playground";
33+
2734
const SessionListInputOptionsSchema = z.object({
2835
organizationId: z.string(),
2936
projectId: z.string(),
@@ -87,6 +94,7 @@ export type ListedSession = Prisma.SessionGetPayload<{
8794
externalId: true;
8895
type: true;
8996
taskIdentifier: true;
97+
isTest: true;
9098
tags: true;
9199
metadata: true;
92100
closedAt: true;

apps/webapp/test/sessionsReplicationService.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ describe("SessionsReplicationService", () => {
8080
},
8181
tags: ["user:42", "plan:pro"],
8282
metadata: { plan: "pro", seats: 3 },
83+
isTest: true,
8384
},
8485
});
8586

@@ -108,6 +109,7 @@ describe("SessionsReplicationService", () => {
108109
environment_type: "DEVELOPMENT",
109110
task_identifier: "my-agent",
110111
tags: ["user:42", "plan:pro"],
112+
is_test: 1,
111113
_is_deleted: 0,
112114
})
113115
);

0 commit comments

Comments
 (0)