Skip to content

Commit 3c492bc

Browse files
authored
feat: Run metadata (#1357)
* Run metadata * Remove metadata from context, move it to it’s own tab * More run metadata stuff - Add metadata to testing - Make using metadata outside of runs a no-op - Add docs * Replaying should copy over the metadata * transfer final attempt output to the task run * A couple of minor fixes * Use the new clientOrThrow() method everywhere * Cleaned up the update metadata endpoint and added an API doc page for it * Mirror task run attempt errors and output
1 parent d74783c commit 3c492bc

File tree

48 files changed

+1292
-219
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1292
-219
lines changed

.changeset/tasty-rats-rhyme.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
Add Run metadata to allow for storing up to 4KB of data on a run and update it during the run

apps/webapp/app/components/code/JSONEditor.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ export function JSONEditor(opts: JSONEditorProps) {
112112
return (
113113
<div
114114
className={cn(
115-
opts.className,
116115
"grid",
117-
showButtons ? "grid-rows-[2.5rem_1fr]" : "grid-rows-[1fr]"
116+
showButtons ? "grid-rows-[2.5rem_1fr]" : "grid-rows-[1fr]",
117+
opts.className
118118
)}
119119
>
120120
{showButtons && (

apps/webapp/app/components/primitives/Tabs.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export function TabButton({
9797
return (
9898
<button
9999
className={cn("group flex flex-col items-center pt-1", props.className)}
100+
type="button"
100101
ref={ref}
101102
{...props}
102103
>

apps/webapp/app/env.server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ const EnvironmentSchema = z.object({
212212
MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000),
213213
TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB
214214
TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB
215+
TASK_RUN_METADATA_MAXIMUM_SIZE: z.coerce.number().int().default(4_096), // 4KB
215216
});
216217

217218
export type Environment = z.infer<typeof EnvironmentSchema>;

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const commonRunSelect = {
2929
completedAt: true,
3030
expiredAt: true,
3131
delayUntil: true,
32+
metadata: true,
33+
metadataType: true,
3234
ttl: true,
3335
tags: true,
3436
costInCents: true,
@@ -157,10 +159,8 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
157159
}
158160
}
159161

160-
const apiStatus = ApiRetrieveRunPresenter.apiStatusFromRunStatus(taskRun.status);
161-
162162
return {
163-
...createCommonRunStructure(taskRun),
163+
...(await createCommonRunStructure(taskRun)),
164164
payload: $payload,
165165
payloadPresignedUrl: $payloadPresignedUrl,
166166
output: $output,
@@ -191,11 +191,15 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
191191
error: ApiRetrieveRunPresenter.apiErrorFromError(a.error),
192192
})),
193193
relatedRuns: {
194-
root: taskRun.rootTaskRun ? createCommonRunStructure(taskRun.rootTaskRun) : undefined,
194+
root: taskRun.rootTaskRun
195+
? await createCommonRunStructure(taskRun.rootTaskRun)
196+
: undefined,
195197
parent: taskRun.parentTaskRun
196-
? createCommonRunStructure(taskRun.parentTaskRun)
198+
? await createCommonRunStructure(taskRun.parentTaskRun)
197199
: undefined,
198-
children: taskRun.childRuns.map((r) => createCommonRunStructure(r)),
200+
children: await Promise.all(
201+
taskRun.childRuns.map(async (r) => await createCommonRunStructure(r))
202+
),
199203
},
200204
};
201205
});
@@ -329,7 +333,12 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
329333
}
330334
}
331335

332-
function createCommonRunStructure(run: CommonRelatedRun) {
336+
async function createCommonRunStructure(run: CommonRelatedRun) {
337+
const metadata = await parsePacket({
338+
data: run.metadata ?? undefined,
339+
dataType: run.metadataType,
340+
});
341+
333342
return {
334343
id: run.friendlyId,
335344
taskIdentifier: run.taskIdentifier,
@@ -354,6 +363,7 @@ function createCommonRunStructure(run: CommonRelatedRun) {
354363
...ApiRetrieveRunPresenter.apiBooleanHelpersFromTaskRunStatus(run.status),
355364
triggerFunction: resolveTriggerFunction(run),
356365
batchId: run.batch?.friendlyId,
366+
metadata,
357367
};
358368
}
359369

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { MachinePresetName, prettyPrintPacket, TaskRunError } from "@trigger.dev/core/v3";
1+
import {
2+
MachinePresetName,
3+
parsePacket,
4+
prettyPrintPacket,
5+
TaskRunError,
6+
} from "@trigger.dev/core/v3";
27
import { RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus";
38
import { eventRepository } from "~/v3/eventRepository.server";
49
import { machinePresetFromName } from "~/v3/machinePresets.server";
@@ -113,6 +118,8 @@ export class SpanPresenter extends BasePresenter {
113118
},
114119
payload: true,
115120
payloadType: true,
121+
metadata: true,
122+
metadataType: true,
116123
maxAttempts: true,
117124
project: {
118125
include: {
@@ -185,6 +192,10 @@ export class SpanPresenter extends BasePresenter {
185192

186193
const span = await eventRepository.getSpan(spanId, run.traceId);
187194

195+
const metadata = run.metadata
196+
? await prettyPrintPacket(run.metadata, run.metadataType)
197+
: undefined;
198+
188199
const context = {
189200
task: {
190201
id: run.taskIdentifier,
@@ -272,6 +283,7 @@ export class SpanPresenter extends BasePresenter {
272283
error,
273284
links: span?.links,
274285
context: JSON.stringify(context, null, 2),
286+
metadata,
275287
};
276288
}
277289

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

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3";
2-
import {
3-
RuntimeEnvironmentType,
4-
TaskRunAttemptStatus,
5-
TaskRunStatus,
6-
TaskTriggerSource,
7-
} from "@trigger.dev/database";
8-
import { sqlDatabaseSchema, PrismaClient, prisma } from "~/db.server";
2+
import { RuntimeEnvironmentType, TaskRunStatus } from "@trigger.dev/database";
3+
import { PrismaClient, prisma, sqlDatabaseSchema } from "~/db.server";
94
import { getTimezones } from "~/utils/timezones.server";
105
import { getUsername } from "~/utils/username";
116

@@ -51,6 +46,8 @@ type RawRun = {
5146
payload: string;
5247
payloadType: string;
5348
runtimeEnvironmentId: string;
49+
metadata?: string;
50+
metadataType?: string;
5451
};
5552

5653
export type StandardRun = Omit<RawRun, "number"> & {
@@ -131,6 +128,8 @@ export class TestTaskPresenter {
131128
taskr.status,
132129
taskr.payload,
133130
taskr."payloadType",
131+
taskr.metadata,
132+
taskr."metadataType",
134133
taskr."runtimeEnvironmentId"
135134
FROM
136135
taskruns AS taskr
@@ -166,6 +165,9 @@ export class TestTaskPresenter {
166165
...r,
167166
number,
168167
payload: await prettyPrintPacket(r.payload, r.payloadType),
168+
metadata: r.metadata
169+
? await prettyPrintPacket(r.metadata, r.metadataType)
170+
: undefined,
169171
};
170172
})
171173
),

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test.tasks.$taskParam/route.tsx

+97-31
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ import {
2727
ResizablePanelGroup,
2828
} from "~/components/primitives/Resizable";
2929
import { Select } from "~/components/primitives/Select";
30+
import { TabButton, TabContainer } from "~/components/primitives/Tabs";
3031
import { TextLink } from "~/components/primitives/TextLink";
3132
import { TaskRunStatusCombo } from "~/components/runs/v3/TaskRunStatus";
3233
import { TimezoneList } from "~/components/scheduled/timezones";
34+
import { useSearchParams } from "~/hooks/useSearchParam";
3335
import { redirectBackWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
3436
import {
3537
ScheduledRun,
@@ -39,6 +41,7 @@ import {
3941
} from "~/presenters/v3/TestTaskPresenter.server";
4042
import { logger } from "~/services/logger.server";
4143
import { requireUserId } from "~/services/session.server";
44+
import { cn } from "~/utils/cn";
4245
import { docsPath, v3RunSpanPath, v3TaskParamsSchema } from "~/utils/pathBuilder";
4346
import { TestTaskService } from "~/v3/services/testTask.server";
4447
import { OutOfEntitlementError } from "~/v3/services/triggerTask.server";
@@ -129,27 +132,44 @@ export default function Page() {
129132
const startingJson = "{\n\n}";
130133

131134
function StandardTaskForm({ task, runs }: { task: TestTask["task"]; runs: StandardRun[] }) {
135+
const { value, replace } = useSearchParams();
136+
const tab = value("tab");
137+
132138
//form submission
133139
const submit = useSubmit();
134140
const lastSubmission = useActionData();
135141

136142
//recent runs
137143
const [selectedCodeSampleId, setSelectedCodeSampleId] = useState(runs.at(0)?.id);
138-
const selectedCodeSample = runs.find((r) => r.id === selectedCodeSampleId)?.payload;
144+
const selectedCodeSample = runs.find((r) => r.id === selectedCodeSampleId);
145+
const selectedCodeSamplePayload = selectedCodeSample?.payload;
146+
const selectedCodeSampleMetadata = selectedCodeSample?.metadata;
139147

140-
const [defaultJson, setDefaultJson] = useState<string>(selectedCodeSample ?? startingJson);
141-
const setCode = useCallback((code: string) => {
142-
setDefaultJson(code);
148+
const [defaultPayloadJson, setDefaultPayloadJson] = useState<string>(
149+
selectedCodeSamplePayload ?? startingJson
150+
);
151+
const setPayload = useCallback((code: string) => {
152+
setDefaultPayloadJson(code);
143153
}, []);
144154

145-
const currentJson = useRef<string>(defaultJson);
155+
const currentPayloadJson = useRef<string>(defaultPayloadJson);
156+
157+
const [defaultMetadataJson, setDefaultMetadataJson] = useState<string>(
158+
selectedCodeSampleMetadata ?? "{}"
159+
);
160+
const setMetadata = useCallback((code: string) => {
161+
setDefaultMetadataJson(code);
162+
}, []);
163+
164+
const currentMetadataJson = useRef<string>(defaultMetadataJson);
146165

147166
const submitForm = useCallback(
148167
(e: React.FormEvent<HTMLFormElement>) => {
149168
submit(
150169
{
151170
triggerSource: "STANDARD",
152-
payload: currentJson.current,
171+
payload: currentPayloadJson.current,
172+
metadata: currentMetadataJson.current,
153173
taskIdentifier: task.taskIdentifier,
154174
environmentId: task.environment.id,
155175
},
@@ -160,7 +180,7 @@ function StandardTaskForm({ task, runs }: { task: TestTask["task"]; runs: Standa
160180
);
161181
e.preventDefault();
162182
},
163-
[currentJson]
183+
[currentPayloadJson, currentMetadataJson]
164184
);
165185

166186
const [form, { environmentId, payload }] = useForm({
@@ -183,28 +203,73 @@ function StandardTaskForm({ task, runs }: { task: TestTask["task"]; runs: Standa
183203
<ResizablePanelGroup orientation="horizontal">
184204
<ResizablePanel id="test-task-main" min="100px" default="60%">
185205
<div className="h-full bg-charcoal-900">
186-
<JSONEditor
187-
defaultValue={defaultJson}
188-
readOnly={false}
189-
basicSetup
190-
onChange={(v) => {
191-
currentJson.current = v;
192-
193-
//deselect the example if it's been edited
194-
if (selectedCodeSampleId) {
195-
if (v !== selectedCodeSample) {
196-
setDefaultJson(v);
197-
setSelectedCodeSampleId(undefined);
206+
<TabContainer className="px-3 pt-2">
207+
<TabButton
208+
isActive={!tab || tab === "payload"}
209+
layoutId="test-editor"
210+
onClick={() => {
211+
replace({ tab: "payload" });
212+
}}
213+
>
214+
Payload
215+
</TabButton>
216+
217+
<TabButton
218+
isActive={tab === "metadata"}
219+
layoutId="test-editor"
220+
onClick={() => {
221+
replace({ tab: "metadata" });
222+
}}
223+
>
224+
Metadata
225+
</TabButton>
226+
</TabContainer>
227+
<div>
228+
<JSONEditor
229+
defaultValue={defaultPayloadJson}
230+
readOnly={false}
231+
basicSetup
232+
onChange={(v) => {
233+
currentPayloadJson.current = v;
234+
235+
//deselect the example if it's been edited
236+
if (selectedCodeSampleId) {
237+
if (v !== selectedCodeSamplePayload) {
238+
setDefaultPayloadJson(v);
239+
setSelectedCodeSampleId(undefined);
240+
}
198241
}
199-
}
200-
}}
201-
height="100%"
202-
min-height="100%"
203-
max-height="100%"
204-
autoFocus
205-
placeholder="Use your schema to enter valid JSON or add one of the recent payloads then click 'Run test'"
206-
className="h-full"
207-
/>
242+
}}
243+
height="100%"
244+
min-height="100%"
245+
max-height="100%"
246+
autoFocus={!tab || tab === "payload"}
247+
placeholder="{ }"
248+
className={cn("h-full", tab === "metadata" && "hidden")}
249+
/>
250+
<JSONEditor
251+
defaultValue={defaultMetadataJson}
252+
readOnly={false}
253+
basicSetup
254+
onChange={(v) => {
255+
currentMetadataJson.current = v;
256+
257+
//deselect the example if it's been edited
258+
if (selectedCodeSampleId) {
259+
if (v !== selectedCodeSampleMetadata) {
260+
setDefaultMetadataJson(v);
261+
setSelectedCodeSampleId(undefined);
262+
}
263+
}
264+
}}
265+
height="100%"
266+
min-height="100%"
267+
max-height="100%"
268+
autoFocus={tab === "metadata"}
269+
placeholder=""
270+
className={cn("h-full", tab !== "metadata" && "hidden")}
271+
/>
272+
</div>
208273
</div>
209274
</ResizablePanel>
210275
<ResizableHandle id="test-task-handle" />
@@ -213,9 +278,10 @@ function StandardTaskForm({ task, runs }: { task: TestTask["task"]; runs: Standa
213278
runs={runs}
214279
selectedId={selectedCodeSampleId}
215280
onSelected={(id) => {
216-
const payload = runs.find((r) => r.id === id)?.payload;
217-
if (!payload) return;
218-
setCode(payload);
281+
const run = runs.find((r) => r.id === id);
282+
if (!run) return;
283+
setPayload(run.payload);
284+
run.metadata && setMetadata(run.metadata);
219285
setSelectedCodeSampleId(id);
220286
}}
221287
/>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test/route.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,13 @@ export default function Page() {
7575
const navigation = useNavigation();
7676

7777
const location = useLocation();
78+
const locationSearchParams = new URLSearchParams(location.search);
79+
const navigationSearchParams = new URLSearchParams(navigation.location?.search);
80+
7881
const isLoadingTasks =
79-
navigation.state === "loading" && navigation.location.pathname === location.pathname;
82+
navigation.state === "loading" &&
83+
navigation.location.pathname === location.pathname &&
84+
navigationSearchParams.get("environment") !== locationSearchParams.get("environment");
8085

8186
return (
8287
<PageContainer>

0 commit comments

Comments
 (0)