Skip to content

Commit 00407cc

Browse files
committed
adds processing step plan
1 parent dc44cc7 commit 00407cc

File tree

11 files changed

+2744
-321
lines changed

11 files changed

+2744
-321
lines changed

apps/api/src/routes/api/processing.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,117 @@ const StepIdParamSchema = z.object({
230230
stepId: z.string(),
231231
});
232232

233+
// Reprocess a job with the latest feed config
234+
processingRoutes.post(
235+
"/jobs/:jobId/reprocess",
236+
zValidator("param", JobIdParamSchema),
237+
async (c) => {
238+
const { jobId } = c.req.valid("param");
239+
const sp = c.var.sp;
240+
241+
try {
242+
const processingService = sp.getProcessingService();
243+
const newJob = await processingService.reprocessWithLatestConfig(jobId);
244+
245+
return c.json(
246+
ProcessingJobRetryResponseSchema.parse({
247+
statusCode: 200,
248+
success: true,
249+
data: {
250+
job: newJob,
251+
message: "Job reprocessing initiated successfully.",
252+
},
253+
}),
254+
);
255+
} catch (error: unknown) {
256+
sp.getLogger().error(
257+
{ error, jobId },
258+
"Error in processingRoutes.post('/jobs/:jobId/reprocess')",
259+
);
260+
261+
if (error instanceof NotFoundError || error instanceof ServiceError) {
262+
return c.json(
263+
ApiErrorResponseSchema.parse({
264+
statusCode: error.statusCode as ContentfulStatusCode,
265+
success: false,
266+
error: { message: error.message },
267+
}),
268+
error.statusCode as ContentfulStatusCode,
269+
);
270+
}
271+
272+
return c.json(
273+
ApiErrorResponseSchema.parse({
274+
statusCode: 500,
275+
success: false,
276+
error: { message: "Failed to reprocess job" },
277+
}),
278+
500,
279+
);
280+
}
281+
},
282+
);
283+
284+
// Tweak a step's input and reprocess from that point
285+
const TweakStepBodySchema = z.object({
286+
newInput: z.string(),
287+
});
288+
289+
processingRoutes.post(
290+
"/steps/:stepId/tweak",
291+
zValidator("param", StepIdParamSchema),
292+
zValidator("json", TweakStepBodySchema),
293+
async (c) => {
294+
const { stepId } = c.req.valid("param");
295+
const { newInput } = c.req.valid("json");
296+
const sp = c.var.sp;
297+
298+
try {
299+
const processingService = sp.getProcessingService();
300+
const newJob = await processingService.tweakAndReprocessStep(
301+
stepId,
302+
newInput,
303+
);
304+
305+
return c.json(
306+
ProcessingJobRetryResponseSchema.parse({
307+
statusCode: 200,
308+
success: true,
309+
data: {
310+
job: newJob,
311+
message: "Step tweak and reprocess initiated successfully.",
312+
},
313+
}),
314+
);
315+
} catch (error: unknown) {
316+
sp.getLogger().error(
317+
{ error, stepId },
318+
"Error in processingRoutes.post('/steps/:stepId/tweak')",
319+
);
320+
321+
if (error instanceof NotFoundError || error instanceof ServiceError) {
322+
return c.json(
323+
ApiErrorResponseSchema.parse({
324+
statusCode: error.statusCode as ContentfulStatusCode,
325+
success: false,
326+
error: { message: error.message },
327+
}),
328+
error.statusCode as ContentfulStatusCode,
329+
);
330+
}
331+
332+
return c.json(
333+
ApiErrorResponseSchema.parse({
334+
statusCode: 500,
335+
success: false,
336+
error: { message: "Failed to tweak and reprocess step" },
337+
}),
338+
500,
339+
);
340+
}
341+
},
342+
);
343+
233344
// Retry processing from a specific failed step
234345
processingRoutes.post(
235346
"/steps/:stepId/retry",
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { Button } from "@/components/ui/button";
2+
import {
3+
Tooltip,
4+
TooltipContent,
5+
TooltipProvider,
6+
TooltipTrigger,
7+
} from "@/components/ui/tooltip";
8+
import {
9+
useReprocessJob,
10+
useRetryProcessingJob,
11+
useRetryProcessingStep,
12+
useTweakAndReprocessStep,
13+
} from "@/lib/api/processing";
14+
import type { ProcessingJob, ProcessingStep } from "@curatedotfun/types";
15+
import { Loader2, RefreshCcw, RotateCw, Wand2 } from "lucide-react";
16+
17+
interface ProcessingActionsProps {
18+
job: ProcessingJob;
19+
step?: ProcessingStep;
20+
}
21+
22+
export function ProcessingActions({ job, step }: ProcessingActionsProps) {
23+
const retryJob = useRetryProcessingJob();
24+
const retryStep = useRetryProcessingStep();
25+
const reprocessJob = useReprocessJob();
26+
const tweakAndReprocessStep = useTweakAndReprocessStep();
27+
28+
const handleRetry = () => {
29+
if (step) {
30+
retryStep.mutate({ stepId: step.id });
31+
} else {
32+
retryJob.mutate({ jobId: job.id });
33+
}
34+
};
35+
36+
const handleReprocessWithLatestConfig = () => {
37+
reprocessJob.mutate({ jobId: job.id });
38+
};
39+
40+
const handleTweakAndReprocess = (newInput: string) => {
41+
if (step) {
42+
tweakAndReprocessStep.mutate({ stepId: step.id, newInput });
43+
}
44+
};
45+
46+
return (
47+
<TooltipProvider>
48+
<div className="flex items-center gap-2">
49+
{step && (
50+
<Tooltip>
51+
<TooltipTrigger asChild>
52+
<Button
53+
variant="outline"
54+
size="sm"
55+
onClick={() =>
56+
handleTweakAndReprocess(JSON.stringify(step.input, null, 2))
57+
}
58+
disabled={tweakAndReprocessStep.isPending}
59+
>
60+
{tweakAndReprocessStep.isPending ? (
61+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
62+
) : (
63+
<Wand2 className="mr-2 h-4 w-4" />
64+
)}
65+
Tweak
66+
</Button>
67+
</TooltipTrigger>
68+
<TooltipContent>
69+
<p>
70+
Modify the input of this step and re-run the job from this
71+
point.
72+
</p>
73+
</TooltipContent>
74+
</Tooltip>
75+
)}
76+
<Tooltip>
77+
<TooltipTrigger asChild>
78+
<Button
79+
variant="outline"
80+
size="sm"
81+
onClick={handleReprocessWithLatestConfig}
82+
disabled={reprocessJob.isPending}
83+
>
84+
{reprocessJob.isPending ? (
85+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
86+
) : (
87+
<RefreshCcw className="mr-2 h-4 w-4" />
88+
)}
89+
Reprocess
90+
</Button>
91+
</TooltipTrigger>
92+
<TooltipContent>
93+
<p>
94+
Create a new processing job for this submission using the latest
95+
feed configuration.
96+
</p>
97+
</TooltipContent>
98+
</Tooltip>
99+
<Tooltip>
100+
<TooltipTrigger asChild>
101+
<Button
102+
variant="outline"
103+
size="sm"
104+
onClick={handleRetry}
105+
disabled={retryJob.isPending || retryStep.isPending}
106+
>
107+
{retryJob.isPending || retryStep.isPending ? (
108+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
109+
) : (
110+
<RotateCw className="mr-2 h-4 w-4" />
111+
)}
112+
{step ? "Retry Step" : "Retry Failed"}
113+
</Button>
114+
</TooltipTrigger>
115+
<TooltipContent>
116+
<p>
117+
{step
118+
? "Retry the job starting from this specific step."
119+
: "Retry all failed steps in this job."}
120+
</p>
121+
</TooltipContent>
122+
</Tooltip>
123+
</div>
124+
</TooltipProvider>
125+
);
126+
}

apps/app/src/components/processing/ProcessingHistory.tsx

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
useProcessingJobs,
3-
useProcessingSteps,
4-
useRetryProcessingJob,
5-
} from "@/lib/api";
1+
import { useProcessingJobs, useProcessingSteps } from "@/lib/api";
62
import { ProcessingJob, ProcessingStep } from "@curatedotfun/types";
73
import {
84
createColumnHelper,
@@ -11,6 +7,7 @@ import {
117
useReactTable,
128
} from "@tanstack/react-table";
139
import { useState } from "react";
10+
import { ProcessingActions } from "./ProcessingActions";
1411
import { ProcessingStepDetails } from "./ProcessingStepDetails";
1512
import { Button } from "../ui/button";
1613
import {
@@ -23,7 +20,7 @@ import {
2320
} from "../ui/table";
2421
import { Badge } from "../ui/badge";
2522
import { format } from "date-fns";
26-
import { Loader2, RefreshCw } from "lucide-react";
23+
import { Loader2 } from "lucide-react";
2724

2825
interface ProcessingHistoryProps {
2926
submissionId: string;
@@ -55,8 +52,6 @@ export function ProcessingHistory({
5552
error: stepsError,
5653
} = useProcessingSteps(selectedJobId);
5754

58-
const { mutate: retryJob, isPending: isRetrying } = useRetryProcessingJob();
59-
6055
// Job columns
6156
const jobColumns = [
6257
jobColumnHelper.accessor("id", {
@@ -98,32 +93,21 @@ export function ProcessingHistory({
9893
jobColumnHelper.display({
9994
id: "actions",
10095
header: "Actions",
101-
cell: (info) => (
102-
<div className="flex gap-2">
103-
<Button
104-
variant="outline"
105-
size="sm"
106-
onClick={() => setSelectedJobId(info.row.original.id)}
107-
>
108-
View Steps
109-
</Button>
110-
{info.row.original.status === "failed" && (
96+
cell: (info) => {
97+
const job = info.row.original;
98+
return (
99+
<div className="flex items-center gap-2">
111100
<Button
112-
variant="destructive"
101+
variant="outline"
113102
size="sm"
114-
onClick={() => {
115-
retryJob({ jobId: info.row.original.id });
116-
}}
117-
disabled={isRetrying}
103+
onClick={() => setSelectedJobId(job.id)}
118104
>
119-
<RefreshCw
120-
className={`mr-2 h-4 w-4 ${isRetrying ? "animate-spin" : ""}`}
121-
/>
122-
Retry
105+
View Steps
123106
</Button>
124-
)}
125-
</div>
126-
),
107+
<ProcessingActions job={job} />
108+
</div>
109+
);
110+
},
127111
}),
128112
];
129113

@@ -178,20 +162,32 @@ export function ProcessingHistory({
178162
: "N/A",
179163
}),
180164
stepColumnHelper.display({
181-
id: "details",
182-
header: "Details",
183-
cell: (info) => (
184-
<Button
185-
variant="ghost"
186-
size="sm"
187-
onClick={() => {
188-
setSelectedStep(info.row.original);
189-
setIsStepDetailsOpen(true);
190-
}}
191-
>
192-
View Details
193-
</Button>
194-
),
165+
id: "actions",
166+
header: "Actions",
167+
cell: (info) => {
168+
const step = info.row.original;
169+
const job = jobs?.find((j) => j.id === step.jobId);
170+
171+
if (!job) {
172+
return null;
173+
}
174+
175+
return (
176+
<div className="flex items-center gap-2">
177+
<Button
178+
variant="ghost"
179+
size="sm"
180+
onClick={() => {
181+
setSelectedStep(step);
182+
setIsStepDetailsOpen(true);
183+
}}
184+
>
185+
View Details
186+
</Button>
187+
<ProcessingActions job={job} step={step} />
188+
</div>
189+
);
190+
},
195191
}),
196192
];
197193

0 commit comments

Comments
 (0)