Skip to content

Commit aaa4f28

Browse files
committed
feat(webapp): derive the runs-list Postgres select from visible columns
The list select is now built from the columns actually shown. A run's payload and output are large, so they are only hydrated when a smart column references them; everything else the presenter needs stays selected regardless.
1 parent 10b7949 commit aaa4f28

5 files changed

Lines changed: 126 additions & 40 deletions

File tree

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import { machinePresetFromRun } from "~/v3/machinePresets.server";
2525
import { ServiceValidationError } from "~/v3/services/baseService.server";
2626
import { isCancellableRunStatus, isFinalRunStatus, isPendingRunStatus } from "~/v3/taskStatus";
2727
import { runTriggeredAt } from "~/v3/runTimestamps";
28+
import {
29+
deriveRunSelect,
30+
type RunColumnId,
31+
type SmartColumnSource,
32+
} from "~/components/runs/v3/runColumns";
2833

2934
// Positive-only cache: only envs known to have runs are stored (empty envs are re-checked),
3035
// so "has runs" is monotonic and the TTL can be very long. Tiered memory + Redis.
@@ -81,6 +86,15 @@ export type RunListOptions = {
8186
pageSize?: number;
8287
// Run the empty-state "has any run ever" probe. Only the runs list consumes it.
8388
includeHasAnyRuns?: boolean;
89+
/**
90+
* Visible-column set used to derive the Postgres select. Omitted => the
91+
* default select (all fields, no payload/output). Provided by the list route
92+
* so payload/output are only hydrated when a smart column references them.
93+
*/
94+
columns?: {
95+
visibleStandardIds: RunColumnId[];
96+
smartSources: SmartColumnSource[];
97+
};
8498
};
8599

86100
const DEFAULT_PAGE_SIZE = 25;
@@ -159,6 +173,7 @@ export class NextRunListPresenter {
159173
cursor,
160174
pageSize = DEFAULT_PAGE_SIZE,
161175
includeHasAnyRuns = false,
176+
columns,
162177
}: RunListOptions
163178
) {
164179
//get the time values from the raw values (including a default period)
@@ -255,7 +270,12 @@ export class NextRunListPresenter {
255270
return date > now ? now : date;
256271
}
257272

273+
const runSelect = columns
274+
? deriveRunSelect(columns.visibleStandardIds, columns.smartSources)
275+
: undefined;
276+
258277
const { runs, pagination } = await runsRepository.listRuns({
278+
runSelect,
259279
organizationId,
260280
environmentId,
261281
projectId,
@@ -335,6 +355,10 @@ export class NextRunListPresenter {
335355
rootTaskRunId: run.rootTaskRunId,
336356
metadata: run.metadata,
337357
metadataType: run.metadataType,
358+
payload: run.payload,
359+
payloadType: run.payloadType,
360+
output: run.output,
361+
outputType: run.outputType,
338362
machinePreset: run.machinePreset ? machinePresetFromRun(run)?.name : undefined,
339363
queue: {
340364
name: run.queue.replace("task/", ""),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {
2+
resolveColumnLayout,
3+
visibleSmartSources,
4+
visibleStandardIds,
5+
type RunColumnId,
6+
type SmartColumnSource,
7+
} from "~/components/runs/v3/runColumns";
8+
9+
/**
10+
* Read the runs-list column state (`cols`/`sc`) off the request and resolve the
11+
* column set the presenter needs to derive its Postgres select. Gates are
12+
* resolved permissively here because they do not affect the always-selected
13+
* fields; only the referenced smart-column sources change what is hydrated.
14+
*/
15+
export function getRunColumnsForSelect(request: Request): {
16+
visibleStandardIds: RunColumnId[];
17+
smartSources: SmartColumnSource[];
18+
} {
19+
const url = new URL(request.url);
20+
const layout = resolveColumnLayout(
21+
{ cols: url.searchParams.getAll("cols"), sc: url.searchParams.getAll("sc") },
22+
{ isManagedCloud: true, isDevelopment: false }
23+
);
24+
25+
return {
26+
visibleStandardIds: visibleStandardIds(layout.visible),
27+
smartSources: visibleSmartSources(layout.visible),
28+
};
29+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { findProjectBySlug } from "~/models/project.server";
5252
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
5353
import { getRunFiltersFromRequest } from "~/presenters/RunFilters.server";
5454
import { NextRunListPresenter } from "~/presenters/v3/NextRunListPresenter.server";
55+
import { getRunColumnsForSelect } from "~/presenters/v3/runColumnsFromRequest.server";
5556
import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server";
5657
import {
5758
setRootOnlyFilterPreference,
@@ -123,6 +124,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
123124
projectId: project.id,
124125
...filters,
125126
includeHasAnyRuns: true,
127+
columns: getRunColumnsForSelect(request),
126128
});
127129

128130
// Only persist rootOnly when no tasks are filtered. While a task filter is active,

apps/webapp/app/services/runsRepository/clickhouseRunsRepository.server.ts

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,51 @@ import { decodeRunsCursor, encodeRunsCursor } from "./runsCursor.server";
1515
import { runStore } from "~/v3/runStore.server";
1616
import { type PrismaClientOrTransaction } from "~/db.server";
1717

18-
import { boundedIn } from "@trigger.dev/database";
18+
import { boundedIn, type Prisma } from "@trigger.dev/database";
19+
import { type ListedRun } from "./runsRepository.server";
1920
type RunCursorRow = { runId: string; createdAt: number };
2021

22+
/**
23+
* Default hydrate select for the runs list, used when a caller does not derive
24+
* one from the visible columns (bulk actions, the live poll). Kept in sync with
25+
* the `ListedRun` payload type.
26+
*/
27+
const LIST_RUN_DEFAULT_SELECT = {
28+
id: true,
29+
friendlyId: true,
30+
taskIdentifier: true,
31+
taskVersion: true,
32+
runtimeEnvironmentId: true,
33+
status: true,
34+
createdAt: true,
35+
queueTimestamp: true,
36+
scheduleId: true,
37+
startedAt: true,
38+
lockedAt: true,
39+
delayUntil: true,
40+
updatedAt: true,
41+
completedAt: true,
42+
isTest: true,
43+
spanId: true,
44+
idempotencyKey: true,
45+
ttl: true,
46+
expiredAt: true,
47+
costInCents: true,
48+
baseCostInCents: true,
49+
usageDurationMs: true,
50+
runTags: true,
51+
depth: true,
52+
rootTaskRunId: true,
53+
batchId: true,
54+
metadata: true,
55+
metadataType: true,
56+
machinePreset: true,
57+
queue: true,
58+
workerQueue: true,
59+
region: true,
60+
annotations: true,
61+
} satisfies Prisma.TaskRunSelect;
62+
2163
/**
2264
* Hydrates a set of rows for a ClickHouse-derived run-id set against the given
2365
* read client. The closure MUST select `id` so `#hydrateRunsByIds` can key
@@ -264,52 +306,22 @@ export class ClickHouseRunsRepository implements IRunsRepository {
264306

265307
const store = this.options.runStore ?? runStore;
266308

267-
let runs = await this.#hydrateRunsByIds(runIds, (client, ids) =>
309+
const select: Prisma.TaskRunSelect = options.runSelect
310+
? { ...options.runSelect, id: true }
311+
: LIST_RUN_DEFAULT_SELECT;
312+
313+
let runs = await this.#hydrateRunsByIds<ListedRun>(runIds, (client, ids) =>
268314
store.findRuns(
269315
{
270316
where: {
271317
id: {
272318
in: boundedIn(ids),
273319
},
274320
},
275-
select: {
276-
id: true,
277-
friendlyId: true,
278-
taskIdentifier: true,
279-
taskVersion: true,
280-
runtimeEnvironmentId: true,
281-
status: true,
282-
createdAt: true,
283-
queueTimestamp: true,
284-
scheduleId: true,
285-
startedAt: true,
286-
lockedAt: true,
287-
delayUntil: true,
288-
updatedAt: true,
289-
completedAt: true,
290-
isTest: true,
291-
spanId: true,
292-
idempotencyKey: true,
293-
ttl: true,
294-
expiredAt: true,
295-
costInCents: true,
296-
baseCostInCents: true,
297-
usageDurationMs: true,
298-
runTags: true,
299-
depth: true,
300-
rootTaskRunId: true,
301-
batchId: true,
302-
metadata: true,
303-
metadataType: true,
304-
machinePreset: true,
305-
queue: true,
306-
workerQueue: true,
307-
region: true,
308-
annotations: true,
309-
},
321+
select,
310322
},
311323
client
312-
)
324+
) as Promise<ListedRun[]>
313325
);
314326

315327
// ClickHouse is slightly delayed, so we're going to do in-memory status filtering too

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,28 @@ export type ListedRun = Prisma.TaskRunGetPayload<{
127127
region: true;
128128
annotations: true;
129129
};
130-
}>;
130+
}> & {
131+
/**
132+
* Large source blobs hydrated only when a smart column references them (see
133+
* `runSelect`). Absent from the default list select.
134+
*/
135+
payload?: string;
136+
payloadType?: string;
137+
output?: string | null;
138+
outputType?: string;
139+
};
131140

132-
export type ListRunsOptions = RunListInputOptions & Pagination;
141+
export type ListRunsOptions = RunListInputOptions &
142+
Pagination & {
143+
/**
144+
* Overrides the default list `select`. The runs list derives this from the
145+
* visible columns so only the fields a shown column needs are hydrated (in
146+
* particular payload/output are omitted unless a smart column asks). Must
147+
* include `id` for hydration keying; behaviour-critical fields are enforced
148+
* by the caller's `deriveRunSelect`.
149+
*/
150+
runSelect?: Prisma.TaskRunSelect;
151+
};
133152

134153
export type TagListOptions = {
135154
organizationId: string;

0 commit comments

Comments
 (0)