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