Skip to content

Commit eb0263e

Browse files
authored
feat(server): add two admin endpoints for queue and environment concurrency debugging and repairing (#2559)
1 parent 7bf579f commit eb0263e

File tree

7 files changed

+558
-5
lines changed

7 files changed

+558
-5
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
2+
import pMap from "p-map";
3+
import { z } from "zod";
4+
import { $replica, prisma } from "~/db.server";
5+
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
6+
import { determineEngineVersion } from "~/v3/engineVersion.server";
7+
import { engine } from "~/v3/runEngine.server";
8+
9+
const ParamsSchema = z.object({
10+
environmentId: z.string(),
11+
});
12+
13+
const BodySchema = z.object({
14+
dryRun: z.boolean().default(true),
15+
queues: z.array(z.string()).default([]),
16+
});
17+
18+
export async function action({ request, params }: ActionFunctionArgs) {
19+
// Next authenticate the request
20+
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
21+
22+
if (!authenticationResult) {
23+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
24+
}
25+
26+
const user = await prisma.user.findUnique({
27+
where: {
28+
id: authenticationResult.userId,
29+
},
30+
});
31+
32+
if (!user) {
33+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
34+
}
35+
36+
if (!user.admin) {
37+
return json({ error: "You must be an admin to perform this action" }, { status: 403 });
38+
}
39+
40+
const parsedParams = ParamsSchema.parse(params);
41+
42+
const environment = await prisma.runtimeEnvironment.findFirst({
43+
where: {
44+
id: parsedParams.environmentId,
45+
},
46+
include: {
47+
organization: true,
48+
project: true,
49+
orgMember: true,
50+
},
51+
});
52+
53+
if (!environment) {
54+
return json({ error: "Environment not found" }, { status: 404 });
55+
}
56+
57+
const engineVersion = await determineEngineVersion({ environment });
58+
59+
if (engineVersion === "V1") {
60+
return json({ error: "Engine version is V1" }, { status: 400 });
61+
}
62+
63+
const body = await request.json();
64+
const parsedBody = BodySchema.parse(body);
65+
66+
const queues = await $replica.taskQueue.findMany({
67+
where: {
68+
runtimeEnvironmentId: environment.id,
69+
version: "V2",
70+
name: parsedBody.queues.length > 0 ? { in: parsedBody.queues } : undefined,
71+
},
72+
select: {
73+
friendlyId: true,
74+
name: true,
75+
concurrencyLimit: true,
76+
type: true,
77+
paused: true,
78+
},
79+
orderBy: {
80+
orderableName: "asc",
81+
},
82+
});
83+
84+
const repairEnvironmentResults = await engine.repairEnvironment(environment, parsedBody.dryRun);
85+
86+
const repairResults = await pMap(
87+
queues,
88+
async (queue) => {
89+
return engine.repairQueue(environment, queue.name, parsedBody.dryRun);
90+
},
91+
{ concurrency: 5 }
92+
);
93+
94+
return json({ environment: repairEnvironmentResults, queues: repairResults });
95+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { json, LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { z } from "zod";
3+
import { $replica, prisma } from "~/db.server";
4+
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
5+
import { determineEngineVersion } from "~/v3/engineVersion.server";
6+
import { engine } from "~/v3/runEngine.server";
7+
8+
const ParamsSchema = z.object({
9+
environmentId: z.string(),
10+
});
11+
12+
const SearchParamsSchema = z.object({
13+
verbose: z.string().default("0"),
14+
page: z.coerce.number().optional(),
15+
per_page: z.coerce.number().optional(),
16+
});
17+
18+
export async function loader({ request, params }: LoaderFunctionArgs) {
19+
// Next authenticate the request
20+
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
21+
22+
if (!authenticationResult) {
23+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
24+
}
25+
26+
const user = await prisma.user.findUnique({
27+
where: {
28+
id: authenticationResult.userId,
29+
},
30+
});
31+
32+
if (!user) {
33+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
34+
}
35+
36+
if (!user.admin) {
37+
return json({ error: "You must be an admin to perform this action" }, { status: 403 });
38+
}
39+
40+
const parsedParams = ParamsSchema.parse(params);
41+
42+
const environment = await prisma.runtimeEnvironment.findFirst({
43+
where: {
44+
id: parsedParams.environmentId,
45+
},
46+
include: {
47+
organization: true,
48+
project: true,
49+
orgMember: true,
50+
},
51+
});
52+
53+
if (!environment) {
54+
return json({ error: "Environment not found" }, { status: 404 });
55+
}
56+
57+
const engineVersion = await determineEngineVersion({ environment });
58+
59+
if (engineVersion === "V1") {
60+
return json({ error: "Engine version is V1" }, { status: 400 });
61+
}
62+
63+
const url = new URL(request.url);
64+
const searchParams = SearchParamsSchema.parse(Object.fromEntries(url.searchParams));
65+
66+
const page = searchParams.page ?? 1;
67+
const perPage = searchParams.per_page ?? 50;
68+
69+
const queues = await $replica.taskQueue.findMany({
70+
where: {
71+
runtimeEnvironmentId: environment.id,
72+
version: "V2",
73+
},
74+
select: {
75+
friendlyId: true,
76+
name: true,
77+
concurrencyLimit: true,
78+
type: true,
79+
paused: true,
80+
},
81+
orderBy: {
82+
orderableName: "asc",
83+
},
84+
skip: (page - 1) * perPage,
85+
take: perPage,
86+
});
87+
88+
const report = await engine.generateEnvironmentReport(
89+
environment,
90+
queues,
91+
searchParams.verbose === "1"
92+
);
93+
94+
return json(report);
95+
}

internal-packages/run-engine/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"nanoid": "3.3.8",
3131
"redlock": "5.0.0-beta.2",
3232
"seedrandom": "^3.0.5",
33-
"zod": "3.25.76"
33+
"zod": "3.25.76",
34+
"p-map": "^6.0.0"
3435
},
3536
"devDependencies": {
3637
"@internal/testcontainers": "workspace:*",

0 commit comments

Comments
 (0)