Skip to content

Commit 4982428

Browse files
committed
API endpoint for running TRQL queries
1 parent 1d744fa commit 4982428

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import { QueryError } from "@internal/clickhouse";
3+
import { z } from "zod";
4+
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
5+
import { executeQuery, type QueryScope } from "~/services/queryService.server";
6+
import { logger } from "~/services/logger.server";
7+
import { rowsToCSV } from "~/utils/dataExport";
8+
9+
const BodySchema = z.object({
10+
query: z.string(),
11+
scope: z.enum(["organization", "project", "environment"]).default("environment"),
12+
period: z.string().nullish(),
13+
from: z.string().nullish(),
14+
to: z.string().nullish(),
15+
format: z.enum(["json", "csv"]).default("json"),
16+
});
17+
18+
const { action, loader } = createActionApiRoute(
19+
{
20+
body: BodySchema,
21+
corsStrategy: "all",
22+
},
23+
async ({ body, authentication }) => {
24+
const { query, scope, period, from, to, format } = body;
25+
const env = authentication.environment;
26+
27+
const queryResult = await executeQuery({
28+
name: "api-query",
29+
query,
30+
scope: scope as QueryScope,
31+
organizationId: env.organization.id,
32+
projectId: env.project.id,
33+
environmentId: env.id,
34+
period,
35+
from,
36+
to,
37+
history: {
38+
source: "API",
39+
},
40+
});
41+
42+
if (!queryResult.success) {
43+
const message =
44+
queryResult.error instanceof QueryError
45+
? queryResult.error.message
46+
: "An unexpected error occurred while executing the query.";
47+
48+
logger.error("Query API error", {
49+
error: queryResult.error,
50+
query,
51+
});
52+
53+
return json({ error: message }, { status: 400 });
54+
}
55+
56+
const { result, periodClipped, maxQueryPeriod } = queryResult;
57+
58+
if (format === "csv") {
59+
const csv = rowsToCSV(result.rows, result.columns);
60+
61+
return new Response(csv, {
62+
status: 200,
63+
headers: {
64+
"Content-Type": "text/csv; charset=utf-8",
65+
"Content-Disposition": "attachment; filename=query-results.csv",
66+
},
67+
});
68+
}
69+
70+
return json({
71+
rows: result.rows,
72+
});
73+
}
74+
);
75+
76+
export { action, loader };

0 commit comments

Comments
 (0)