Skip to content

Commit 835273c

Browse files
committed
Merge remote-tracking branch 'origin/main' into run-navigation-tweaks
# Conflicts: # apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
2 parents 42a3033 + 469808c commit 835273c

File tree

13 files changed

+417
-44
lines changed

13 files changed

+417
-44
lines changed

.changeset/quick-plums-tan.md

Lines changed: 6 additions & 0 deletions
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+
Added support for idempotency reset

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,7 @@ function TimelineView({
983983
const initialTimelineDimensions = useInitialDimensions(timelineContainerRef);
984984
const minTimelineWidth = initialTimelineDimensions?.width ?? 300;
985985
const maxTimelineWidth = minTimelineWidth * 10;
986+
const disableSpansAnimations = rootSpanStatus !== "executing";
986987

987988
//we want to live-update the duration if the root span is still executing
988989
const [duration, setDuration] = useState(queueAdjustedNs(totalDuration, queuedDuration));
@@ -1155,9 +1156,9 @@ function TimelineView({
11551156
eventBackgroundClassName(node.data)
11561157
)}
11571158
layoutId={
1158-
node.data.isPartial ? `${node.id}-${event.name}` : undefined
1159+
disableSpansAnimations ? undefined : `${node.id}-${event.name}`
11591160
}
1160-
animate={!node.data.isPartial ? false : undefined}
1161+
animate={disableSpansAnimations ? false : undefined}
11611162
/>
11621163
)}
11631164
</Timeline.Point>
@@ -1176,9 +1177,9 @@ function TimelineView({
11761177
eventBorderClassName(node.data)
11771178
)}
11781179
layoutId={
1179-
node.data.isPartial ? `${node.id}-${event.name}` : undefined
1180+
disableSpansAnimations ? undefined : `${node.id}-${event.name}`
11801181
}
1181-
animate={!node.data.isPartial ? false : undefined}
1182+
animate={disableSpansAnimations ? false : undefined}
11821183
/>
11831184
)}
11841185
</Timeline.Point>
@@ -1197,8 +1198,8 @@ function TimelineView({
11971198
>
11981199
<motion.div
11991200
className={cn("h-px w-full", eventBackgroundClassName(node.data))}
1200-
layoutId={node.data.isPartial ? `mark-${node.id}` : undefined}
1201-
animate={!node.data.isPartial ? false : undefined}
1201+
layoutId={disableSpansAnimations ? undefined : `mark-${node.id}`}
1202+
animate={disableSpansAnimations ? false : undefined}
12021203
/>
12031204
</Timeline.Span>
12041205
) : null}
@@ -1221,6 +1222,7 @@ function TimelineView({
12211222
}
12221223
node={node}
12231224
fadeLeft={isTopSpan && queuedDuration !== undefined}
1225+
disableAnimations={disableSpansAnimations}
12241226
/>
12251227
</>
12261228
) : (
@@ -1235,8 +1237,8 @@ function TimelineView({
12351237
"-ml-0.5 size-3 rounded-full border-2 border-background-bright",
12361238
eventBackgroundClassName(node.data)
12371239
)}
1238-
layoutId={node.data.isPartial ? node.id : undefined}
1239-
animate={!node.data.isPartial ? false : undefined}
1240+
layoutId={disableSpansAnimations ? undefined : node.id}
1241+
animate={disableSpansAnimations ? false : undefined}
12401242
/>
12411243
)}
12421244
</Timeline.Point>
@@ -1468,8 +1470,14 @@ function SpanWithDuration({
14681470
showDuration,
14691471
node,
14701472
fadeLeft,
1473+
disableAnimations,
14711474
...props
1472-
}: Timeline.SpanProps & { node: TraceEvent; showDuration: boolean; fadeLeft: boolean }) {
1475+
}: Timeline.SpanProps & {
1476+
node: TraceEvent;
1477+
showDuration: boolean;
1478+
fadeLeft: boolean;
1479+
disableAnimations?: boolean;
1480+
}) {
14731481
return (
14741482
<Timeline.Span {...props}>
14751483
<motion.div
@@ -1479,8 +1487,8 @@ function SpanWithDuration({
14791487
fadeLeft ? "rounded-r-sm bg-gradient-to-r from-black/50 to-transparent" : "rounded-sm"
14801488
)}
14811489
style={{ backgroundSize: "20px 100%", backgroundRepeat: "no-repeat" }}
1482-
layoutId={node.data.isPartial ? node.id : undefined}
1483-
animate={!node.data.isPartial ? false : undefined}
1490+
layoutId={disableAnimations ? undefined : node.id}
1491+
animate={disableAnimations ? false : undefined}
14841492
>
14851493
{node.data.isPartial && (
14861494
<div
@@ -1493,12 +1501,12 @@ function SpanWithDuration({
14931501
"sticky left-0 z-10 transition-opacity group-hover:opacity-100",
14941502
!showDuration && "opacity-0"
14951503
)}
1496-
animate={!node.data.isPartial ? false : undefined}
1504+
animate={disableAnimations ? false : undefined}
14971505
>
14981506
<motion.div
14991507
className="whitespace-nowrap rounded-sm px-1 py-0.5 text-xxs text-text-bright text-shadow-custom"
1500-
layout={node.data.isPartial ? "position" : undefined}
1501-
animate={!node.data.isPartial ? false : undefined}
1508+
layout={disableAnimations ? undefined : "position"}
1509+
animate={disableAnimations ? false : undefined}
15021510
>
15031511
{formatDurationMilliseconds(props.durationMs, {
15041512
style: "short",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import { ServiceValidationError } from "~/v3/services/baseService.server";
3+
import { z } from "zod";
4+
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
5+
import { ResetIdempotencyKeyService } from "~/v3/services/resetIdempotencyKey.server";
6+
import { logger } from "~/services/logger.server";
7+
8+
const ParamsSchema = z.object({
9+
key: z.string(),
10+
});
11+
12+
const BodySchema = z.object({
13+
taskIdentifier: z.string().min(1, "Task identifier is required"),
14+
});
15+
16+
export const { action } = createActionApiRoute(
17+
{
18+
params: ParamsSchema,
19+
body: BodySchema,
20+
allowJWT: true,
21+
corsStrategy: "all",
22+
authorization: {
23+
action: "write",
24+
resource: () => ({}),
25+
superScopes: ["write:runs", "admin"],
26+
},
27+
},
28+
async ({ params, body, authentication }) => {
29+
const service = new ResetIdempotencyKeyService();
30+
31+
try {
32+
const result = await service.call(
33+
params.key,
34+
body.taskIdentifier,
35+
authentication.environment
36+
);
37+
return json(result, { status: 200 });
38+
} catch (error) {
39+
if (error instanceof ServiceValidationError) {
40+
return json({ error: error.message }, { status: error.status ?? 400 });
41+
}
42+
43+
logger.error("Failed to reset idempotency key via API", {
44+
error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error),
45+
});
46+
47+
return json({ error: "Internal Server Error" }, { status: 500 });
48+
}
49+
50+
}
51+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { parse } from "@conform-to/zod";
2+
import { type ActionFunction, json } from "@remix-run/node";
3+
import { z } from "zod";
4+
import { prisma } from "~/db.server";
5+
import { jsonWithErrorMessage } from "~/models/message.server";
6+
import { logger } from "~/services/logger.server";
7+
import { requireUserId } from "~/services/session.server";
8+
import { ResetIdempotencyKeyService } from "~/v3/services/resetIdempotencyKey.server";
9+
import { v3RunParamsSchema } from "~/utils/pathBuilder";
10+
11+
export const resetIdempotencyKeySchema = z.object({
12+
taskIdentifier: z.string().min(1, "Task identifier is required"),
13+
});
14+
15+
export const action: ActionFunction = async ({ request, params }) => {
16+
const userId = await requireUserId(request);
17+
const { projectParam, organizationSlug, envParam, runParam } =
18+
v3RunParamsSchema.parse(params);
19+
20+
const formData = await request.formData();
21+
const submission = parse(formData, { schema: resetIdempotencyKeySchema });
22+
23+
if (!submission.value) {
24+
return json(submission);
25+
}
26+
27+
try {
28+
const { taskIdentifier } = submission.value;
29+
30+
const taskRun = await prisma.taskRun.findFirst({
31+
where: {
32+
friendlyId: runParam,
33+
project: {
34+
slug: projectParam,
35+
organization: {
36+
slug: organizationSlug,
37+
members: {
38+
some: {
39+
userId,
40+
},
41+
},
42+
},
43+
},
44+
runtimeEnvironment: {
45+
slug: envParam,
46+
},
47+
},
48+
select: {
49+
id: true,
50+
idempotencyKey: true,
51+
taskIdentifier: true,
52+
runtimeEnvironmentId: true,
53+
},
54+
});
55+
56+
if (!taskRun) {
57+
submission.error = { runParam: ["Run not found"] };
58+
return json(submission);
59+
}
60+
61+
if (!taskRun.idempotencyKey) {
62+
return jsonWithErrorMessage(
63+
submission,
64+
request,
65+
"This run does not have an idempotency key"
66+
);
67+
}
68+
69+
if (taskRun.taskIdentifier !== taskIdentifier) {
70+
submission.error = { taskIdentifier: ["Task identifier does not match this run"] };
71+
return json(submission);
72+
}
73+
74+
const environment = await prisma.runtimeEnvironment.findUnique({
75+
where: {
76+
id: taskRun.runtimeEnvironmentId,
77+
},
78+
include: {
79+
project: {
80+
include: {
81+
organization: true,
82+
},
83+
},
84+
},
85+
});
86+
87+
if (!environment) {
88+
return jsonWithErrorMessage(
89+
submission,
90+
request,
91+
"Environment not found"
92+
);
93+
}
94+
95+
const service = new ResetIdempotencyKeyService();
96+
97+
await service.call(taskRun.idempotencyKey, taskIdentifier, {
98+
...environment,
99+
organizationId: environment.project.organizationId,
100+
organization: environment.project.organization,
101+
});
102+
103+
return json({ success: true });
104+
} catch (error) {
105+
if (error instanceof Error) {
106+
logger.error("Failed to reset idempotency key", {
107+
error: {
108+
name: error.name,
109+
message: error.message,
110+
stack: error.stack,
111+
},
112+
});
113+
return jsonWithErrorMessage(
114+
submission,
115+
request,
116+
`Failed to reset idempotency key: ${error.message}`
117+
);
118+
} else {
119+
logger.error("Failed to reset idempotency key", { error });
120+
return jsonWithErrorMessage(
121+
submission,
122+
request,
123+
`Failed to reset idempotency key: ${JSON.stringify(error)}`
124+
);
125+
}
126+
}
127+
};

0 commit comments

Comments
 (0)