diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/logs-table.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/logs-table.tsx
index 7c6e1de51d..10abf72a49 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/logs-table.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/logs-table.tsx
@@ -52,6 +52,13 @@ export const RatelimitOverviewLogsTable = ({
key: "passed",
header: "Passed",
width: "7.5%",
+ sort: {
+ direction: getSortDirection("passed"),
+ sortable: true,
+ onSort() {
+ toggleSort("passed", false);
+ },
+ },
render: (log) => {
return (
@@ -78,6 +85,13 @@ export const RatelimitOverviewLogsTable = ({
key: "blocked",
header: "Blocked",
width: "7.5%",
+ sort: {
+ direction: getSortDirection("blocked"),
+ sortable: true,
+ onSort() {
+ toggleSort("blocked", false);
+ },
+ },
render: (log) => {
const style = getStatusStyle(log);
return (
@@ -158,7 +172,7 @@ export const RatelimitOverviewLogsTable = ({
direction: getSortDirection("time"),
sortable: true,
onSort() {
- toggleSort("time", true);
+ toggleSort("time", false);
},
},
render: (log) => (
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts
index b55d74191b..b43d48c3ff 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts
@@ -1,7 +1,7 @@
import { z } from "zod";
import { ratelimitOverviewFilterOperatorEnum } from "../../filters.schema";
-export const sortFields = z.enum(["time", "avg_latency", "p99_latency"]);
+export const sortFields = z.enum(["time", "avg_latency", "p99_latency", "blocked", "passed"]);
export const ratelimitQueryOverviewLogsPayload = z.object({
limit: z.number().int(),
startTime: z.number().int(),
diff --git a/internal/clickhouse/src/ratelimits.ts b/internal/clickhouse/src/ratelimits.ts
index d63e23bd1f..82d7f82f85 100644
--- a/internal/clickhouse/src/ratelimits.ts
+++ b/internal/clickhouse/src/ratelimits.ts
@@ -434,7 +434,7 @@ export const ratelimitOverviewLogsParams = z.object({
sorts: z
.array(
z.object({
- column: z.enum(["time", "avg_latency", "p99_latency"]),
+ column: z.enum(["time", "avg_latency", "p99_latency", "blocked", "passed"]),
direction: z.enum(["asc", "desc"]),
}),
)
@@ -514,6 +514,8 @@ export function getRatelimitOverviewLogs(ch: Querier) {
["time", "last_request_time"],
["avg_latency", "avg_latency"],
["p99_latency", "p99_latency"],
+ ["passed", "passed_count"],
+ ["blocked", "blocked_count"],
]);
const orderBy =
@@ -532,9 +534,23 @@ export function getRatelimitOverviewLogs(ch: Querier) {
}, [])
: [];
- // If time is explicitly sorted ASC, maintain that direction
+ // Check if we have custom sorts
+ const hasAvgLatencySort = args.sorts?.some((s) => s.column === "avg_latency");
+ const hasP99LatencySort = args.sorts?.some((s) => s.column === "p99_latency");
+ const hasPassedSort = args.sorts?.some((s) => s.column === "passed");
+ const hasBlockedSort = args.sorts?.some((s) => s.column === "blocked");
+ const hasCustomSort = hasAvgLatencySort || hasP99LatencySort || hasPassedSort || hasBlockedSort;
+
+ // Get explicit time sort if it exists
const timeSort = args.sorts?.find((s) => s.column === "time");
- const timeDirection = timeSort?.direction.toUpperCase() === "ASC" ? "ASC" : "DESC";
+
+ // If we have custom sort (avg_latency, p99_latency, passed, blocked), always use ASC for better pagination
+ // Otherwise use explicit time direction or default to DESC
+ const timeDirection = hasCustomSort
+ ? "ASC"
+ : timeSort?.direction.toUpperCase() === "ASC"
+ ? "ASC"
+ : "DESC";
// Remove any existing time sort from the orderBy array
const orderByWithoutTime = orderBy.filter((clause) => !clause.startsWith("last_request_time"));
@@ -547,6 +563,33 @@ export function getRatelimitOverviewLogs(ch: Querier) {
`request_id ${timeDirection}`,
].join(", ") || "last_request_time DESC, request_id DESC"; // Fallback if empty
+ // Create cursor condition based on time direction
+ let cursorCondition: string;
+
+ // For first page or no cursor provided
+ if (!args.cursorTime || !args.cursorRequestId) {
+ cursorCondition = `
+ AND ({cursorTime: Nullable(UInt64)} IS NULL AND {cursorRequestId: Nullable(String)} IS NULL)
+ `;
+ } else {
+ // For subsequent pages, use cursor based on time direction
+ if (timeDirection === "ASC") {
+ cursorCondition = `
+ AND (
+ (time = {cursorTime: Nullable(UInt64)} AND request_id > {cursorRequestId: Nullable(String)})
+ OR time > {cursorTime: Nullable(UInt64)}
+ )
+ `;
+ } else {
+ cursorCondition = `
+ AND (
+ (time = {cursorTime: Nullable(UInt64)} AND request_id < {cursorRequestId: Nullable(String)})
+ OR time < {cursorTime: Nullable(UInt64)}
+ )
+ `;
+ }
+ }
+
const extendedParamsSchema = ratelimitOverviewLogsParams.extend(paramSchemaExtension);
const query = ch.query({
query: `WITH filtered_ratelimits AS (
@@ -561,9 +604,7 @@ export function getRatelimitOverviewLogs(ch: Querier) {
AND time BETWEEN {startTime: UInt64} AND {endTime: UInt64}
AND (${identifierConditions})
AND (${statusCondition})
- AND (({cursorTime: Nullable(UInt64)} IS NULL AND {cursorRequestId: Nullable(String)} IS NULL)
- OR (time = {cursorTime: Nullable(UInt64)} AND request_id < {cursorRequestId: Nullable(String)})
- OR time < {cursorTime: Nullable(UInt64)})
+ ${cursorCondition}
),
aggregated_data AS (
SELECT