Conversation
|
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
|
|
Thank you for following the naming conventions for pull request titles! 🙏 |
📝 Walkthrough""" WalkthroughThis update introduces a comprehensive refactor and expansion of the API key detail page and its associated logs UI in the dashboard. It replaces the previous server-rendered page and settings components with a new client-driven architecture featuring modular components for log controls, charts, tables, filtering, and permissions. It also adds new backend TRPC procedures and ClickHouse queries for fine-grained log and timeseries data, and removes legacy settings and verification components. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant KeyDetailsLogsClient
participant Controls/Charts/Table Components
participant TRPC Backend
participant ClickHouse
User->>KeyDetailsLogsClient: Loads key details page
KeyDetailsLogsClient->>Controls/Charts/Table Components: Render UI
Controls/Charts/Table Components->>TRPC Backend: Fetch logs/timeseries/permissions
TRPC Backend->>ClickHouse: Query logs/timeseries with filters
ClickHouse-->>TRPC Backend: Return log/timeseries data
TRPC Backend-->>Controls/Charts/Table Components: Return processed data
Controls/Charts/Table Components-->>User: Display logs, charts, controls
User->>Controls/Charts/Table Components: Interact with filters, controls
Controls/Charts/Table Components->>TRPC Backend: Refetch data as needed
Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 29
🔭 Outside diff range comments (1)
internal/clickhouse/src/verifications.ts (1)
16-26: 🛠️ Refactor suggestion
⚠️ Potential issueUnify outcome enum definition to avoid drift and include the empty-string case
insertVerificationre-declares the list ofoutcomeliterals instead of re-usingKEY_VERIFICATION_OUTCOMES.
This already diverges – the constant includes an empty string ("") but the insertion schema does not, so an empty outcome would be accepted by readers (filters) yet rejected on insert.- outcome: z.enum([ - "VALID", - "RATE_LIMITED", - "EXPIRED", - "DISABLED", - "FORBIDDEN", - "USAGE_EXCEEDED", - "INSUFFICIENT_PERMISSIONS", - ]), + outcome: z.enum(KEY_VERIFICATION_OUTCOMES),Re-using the shared constant guarantees one source of truth and prevents hard-to-trace data-quality bugs.
♻️ Duplicate comments (1)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/log-details/index.tsx (1)
96-100: Mirror fix from Request Body blockRemember to replicate the optimisation & null-guard refactor for the response body section to avoid the same
undefinededge case.
🧹 Nitpick comments (38)
apps/dashboard/app/(app)/apis/[apiId]/actions.ts (1)
70-91: Well-designed utility function for key details retrieval.The
getKeyDetailsfunction is efficiently implemented, leveraging the existinggetAllKeysfunction with targeted filtering. It provides proper null handling for cases where the key isn't found.Consider adding a short JSDoc comment describing the function's purpose and parameters for better documentation:
+/** + * Retrieves detailed information for a specific key + * @param keyId - The ID of the key to retrieve + * @param keyspaceId - The keyspace ID containing the key + * @param workspaceId - The workspace ID for authorization + * @returns The key details or null if not found + */ export async function getKeyDetails( keyId: string, keyspaceId: string, workspaceId: string, ): Promise<KeyDetails | null> {apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/log-details/components/hooks/use-logs-query.ts (1)
4-7: Consider using interface instead of type for better extensibility.For consistency and future extensibility, consider using an interface for hook parameters.
-type useFetchRequestDetails = { +interface UseFetchRequestDetailsProps { requestId?: string; -}; +}apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/context/logs.tsx (1)
31-36: Error message doesn't match component namesThe error message references different names than the actual components, which could be confusing for developers.
- throw new Error("useLogsContext must be used within a LogsProvider"); + throw new Error("useKeyDetailsLogsContext must be used within a KeyDetailsLogsProvider");apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/query-logs.schema.ts (1)
10-10: Consider adding format validation for thesincefieldThe
sincefield is currently validated as a string without additional constraints. Consider adding more specific validation if this field is expected to be in a particular format (like ISO date string or time duration format).- since: z.string(), + since: z.string().refine((val) => { + // Add appropriate validation for expected format + return true; // Replace with actual validation + }, "Invalid format for 'since' parameter"),apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/control-cloud/index.tsx (1)
1-31: Well-structured component with a clear responsibility.The
KeysDetailsLogsControlCloudcomponent nicely integrates theControlCloudUI component with the filter state management through theuseFiltershook. TheformatFieldNameutility function provides good user-friendly labels for filter fields.Consider adding JSDoc comments to document the component's purpose and integration within the broader logs UI system. This would improve maintainability as the codebase grows, especially for new developers working on this feature.
+/** + * A component that integrates the ControlCloud UI with filter state management. + * It provides filter display, removal, and update capabilities within the key details logs UI. + */ export const KeysDetailsLogsControlCloud = () => { const { filters, updateFilters, removeFilter } = useFilters(); return ( <ControlCloud historicalWindow={HISTORICAL_DATA_WINDOW} formatFieldName={formatFieldName} filters={filters} removeFilter={removeFilter} updateFilters={updateFilters} /> ); };apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-filters/index.tsx (1)
10-51: Well-implemented filter control with good accessibility.The component provides a clean filter UI with proper accessibility attributes, including aria labels and keyboard shortcuts. Good implementation of conditional styling based on the filter state.
If you plan to add more filter categories in the future, consider extracting the filter items array to a separate configuration constant for better maintainability:
+const FILTER_ITEMS = [ + { + id: "outcomes", + label: "Outcomes", + shortcut: "o", + component: (props: { onDrawerClose: () => void }) => ( + <OutcomesFilter onDrawerClose={props.onDrawerClose} /> + ), + }, + // Future filter categories can be added here +]; export const LogsFilters = () => { const { filters } = useFilters(); const [open, setOpen] = useState(false); return ( <FiltersPopover open={open} onOpenChange={setOpen} - items={[ - { - id: "outcomes", - label: "Outcomes", - shortcut: "o", - component: <OutcomesFilter onDrawerClose={() => setOpen(false)} />, - }, - ]} + items={FILTER_ITEMS.map(item => ({ + ...item, + component: item.component({ onDrawerClose: () => setOpen(false) }), + }))} activeFilters={filters} >apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/hooks/use-fetch-timeseries.ts (3)
72-74: Extract magic number to a named constant.The refetch interval of 10,000ms (10 seconds) is a magic number. Consider extracting it to a named constant for clarity and maintainability.
+const LIVE_REFETCH_INTERVAL_MS = 10_000; // 10 seconds export const useFetchVerificationTimeseries = (keyId: string, keyspaceId: string) => { // ...existing code... const { data, isLoading, isError } = trpc.key.logs.timeseries.useQuery(queryParams, { - refetchInterval: queryParams.endTime === timestamp ? 10_000 : false, + refetchInterval: queryParams.endTime === timestamp ? LIVE_REFETCH_INTERVAL_MS : false, });
91-114: Reduce code duplication when mapping outcome fields.The outcome fields mapping has repetitive patterns that can be refactored to reduce duplication.
const result = { displayX: formatTimestampForChart(ts.x, data.granularity), originalTimestamp: ts.x, valid: ts.y.valid, total: ts.y.total, success: ts.y.valid, error: ts.y.total - ts.y.valid, }; const outcomeFields: Record<string, number> = {}; - if (ts.y.rate_limited_count !== undefined) { - outcomeFields.rate_limited = ts.y.rate_limited_count; - } - if (ts.y.insufficient_permissions_count !== undefined) { - outcomeFields.insufficient_permissions = ts.y.insufficient_permissions_count; - } - if (ts.y.forbidden_count !== undefined) { - outcomeFields.forbidden = ts.y.forbidden_count; - } - if (ts.y.disabled_count !== undefined) { - outcomeFields.disabled = ts.y.disabled_count; - } - if (ts.y.expired_count !== undefined) { - outcomeFields.expired = ts.y.expired_count; - } - if (ts.y.usage_exceeded_count !== undefined) { - outcomeFields.usage_exceeded = ts.y.usage_exceeded_count; - } + // Map outcome fields dynamically + const outcomeMapping = { + rate_limited: 'rate_limited_count', + insufficient_permissions: 'insufficient_permissions_count', + forbidden: 'forbidden_count', + disabled: 'disabled_count', + expired: 'expired_count', + usage_exceeded: 'usage_exceeded_count', + }; + + Object.entries(outcomeMapping).forEach(([key, countField]) => { + const count = ts.y[countField as keyof typeof ts.y]; + if (count !== undefined) { + outcomeFields[key] = count as number; + } + });
11-124: Consider adding error handling details.The hook returns
isErrorstate but doesn't provide detailed error information or handling strategies.Consider enhancing error handling to provide more context to the user about what went wrong and possible remediation steps. This could involve:
- Capturing the error message from the TRPC query
- Providing a fallback UI or retry mechanism
- Logging errors for debugging
const { data, isLoading, isError } = trpc.key.logs.timeseries.useQuery(queryParams, { refetchInterval: queryParams.endTime === timestamp ? 10_000 : false, }); +// Add error message capture +const errorMessage = isError + ? "Failed to load verification data. Please try refreshing the page." + : ""; // ...rest of the code return { timeseries: timeseries || [], isLoading, isError, + errorMessage, granularity: data?.granularity, };apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/status-badge.tsx (1)
3-9: Add default props to handle missing values.The component expects all properties in the
primaryobject to be present, but doesn't handle cases where some values might be missing.Add default values for the props to improve component resilience:
type StatusBadgeProps = { primary: { label: string; color: string; - icon: React.ReactNode; + icon?: React.ReactNode; // Make icon optional }; }; export const StatusBadge = ({ primary }: StatusBadgeProps) => { + // Provide defaults for missing values + const { + label = "", + color = "bg-gray-5 text-gray-11", + icon = null + } = primary; + return ( <div className="flex items-center justify-start gap-0.5 text-xs"> <div className={cn( - primary.color, + color, "px-1.5 py-1 flex items-center justify-center gap-2 h-[22px]", "rounded-md", )} > - {primary.icon && <span className="shrink-0">{primary.icon}</span>} - <span>{primary.label}</span> + {icon && <span className="shrink-0">{icon}</span>} + <span>{label}</span> </div> </div> ); };apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx (1)
45-59: Simple and effective UI implementation.The component renders a clean search interface with appropriate props for loading state management and search execution. The example query "Show rate limited outcomes" provides helpful user guidance.
Consider adding more diverse example queries to help users understand the search capabilities better.
- exampleQueries={["Show rate limited outcomes"]} + exampleQueries={[ + "Show rate limited outcomes", + "Find verification errors", + "Show successful verifications in the last hour" + ]}apps/dashboard/lib/trpc/routers/key/query-timeseries/index.ts (1)
14-39: Consider improving error handling by logging the actual error.The error catch block discards the original error (
_err), which could make debugging harder in production scenarios. Consider logging the original error before throwing the TRPC error..catch((_err) => { + console.error("Failed to retrieve key details:", _err); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Failed to retrieve key details due to an error. If this issue persists, please contact support@unkey.dev with the time this occurred.", }); });apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/utils/calculate-blocked-percentage.ts (2)
8-22: Consider extracting severity thresholds as constants.The getErrorPercentage function handles division by zero correctly, but the severity thresholds are hardcoded in the getErrorSeverity function. Consider extracting these as named constants for better maintainability.
+ /** + * Thresholds for determining error severity levels + */ + export const ERROR_SEVERITY_THRESHOLDS = { + HIGH: 50, + MODERATE: 20, + LOW: 0, + }; /** * Calculate the error percentage for a key * @param log The keys overview log * @returns The percentage of errors (0-100) */ export const getErrorPercentage = (log: KeysOverviewLog): number => { const totalRequests = log.valid_count + log.error_count; // Avoid division by zero if (totalRequests === 0) { return 0; } return (log.error_count / totalRequests) * 100; };And then use these constants in the getErrorSeverity function.
39-57: Function for determining error severity needs threshold constants.The getErrorSeverity function uses hardcoded thresholds. Update it to use the constants suggested earlier for better maintainability.
/** * Determine the error severity based on the error percentage * @param log The keys overview log * @returns The severity level as a string literal */ export const getErrorSeverity = (log: KeysOverviewLog): ErrorSeverity => { const errorPercentage = getErrorPercentage(log); - if (errorPercentage >= 50) { + if (errorPercentage >= ERROR_SEVERITY_THRESHOLDS.HIGH) { return "high"; } - if (errorPercentage >= 20) { + if (errorPercentage >= ERROR_SEVERITY_THRESHOLDS.MODERATE) { return "moderate"; } - if (errorPercentage > 0) { + if (errorPercentage > ERROR_SEVERITY_THRESHOLDS.LOW) { return "low"; } return "none"; };apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/index.tsx (2)
25-55: Add a comment explaining the special case when start equals end.The code has special handling when
start === end, but it's not immediately clear why this adjustment is needed. Consider adding a comment to explain this logic.let adjustedEnd = end; + // When a single point is selected (start equals end), we add a buffer based on granularity + // to ensure the selected point is included in the time range if (start === end && granularity) { adjustedEnd = end + getTimeBufferForGranularity(granularity); }
36-39: Consider handling edge case when granularity is undefined.The code checks for
granularitybeing truthy, but doesn't handle the case when it might be undefined. Consider providing a default buffer time for such cases.let adjustedEnd = end; if (start === end && granularity) { adjustedEnd = end + getTimeBufferForGranularity(granularity); + } else if (start === end) { + // Default to a 1-minute buffer if granularity is not available + adjustedEnd = end + 60 * 1000; }apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/query-timeseries.schema.ts (1)
4-4: Consider documenting the purpose of MAX_KEYID_COUNT constantThe
MAX_KEYID_COUNTconstant is defined but not directly used within this schema. Add a comment explaining its purpose and where it's used to improve code clarity.-export const MAX_KEYID_COUNT = 1; +/** + * Maximum number of key IDs that can be queried at once in the timeseries API. + * Currently limited to 1 for individual key detail views. + */ +export const MAX_KEYID_COUNT = 1;apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/index.tsx (2)
38-56: Consider extracting the animation configuration to a constantThe animation configurations are duplicated across multiple motion components. Consider extracting them to reusable constants to improve maintainability.
+const fadeInAnimation = { + initial: { opacity: 0, x: -5 }, + animate: { opacity: 1, x: 0 }, + exit: { opacity: 0, x: -5 }, + transition: { + duration: 0.3, + ease: "easeOut", + }, +}; +const labelAnimation = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + transition: { delay: 0.05, duration: 0.2 }, +}; - <motion.div - className="flex items-center" - initial={{ opacity: 0, x: -5 }} - animate={{ opacity: 1, x: 0 }} - exit={{ opacity: 0, x: -5 }} - transition={{ - duration: 0.3, - ease: "easeOut", - }} - > + <motion.div + className="flex items-center" + {...fadeInAnimation} + >
59-101: Consider extracting duplicate motion configuration to a shared componentThere's significant duplication between the two conditional motion.div components for credits display. Consider extracting this to a shared component to reduce code duplication.
+ const CreditBadge = ({ remainingCredit }: { remainingCredit: number }) => ( + <motion.div + initial={{ opacity: 0, scale: 0.97 }} + animate={{ opacity: 1, scale: 1 }} + transition={{ + delay: 0.1, + duration: 0.2, + scale: { + type: "spring", + stiffness: 500, + damping: 25, + }, + }} + > + <StatusBadge + className="text-xs" + variant={remainingCredit > 0 ? "enabled" : "disabled"} + text={remainingCredit > 0 ? formatNumber(remainingCredit) : "0"} + icon={<Coins size="sm-thin" />} + /> + </motion.div> + ); // Then in the render: - {data.remainingCredit > 0 ? ( - <motion.div - initial={{ opacity: 0, scale: 0.97 }} - animate={{ opacity: 1, scale: 1 }} - transition={{ - delay: 0.1, - duration: 0.2, - scale: { - type: "spring", - stiffness: 500, - damping: 25, - }, - }} - > - <StatusBadge - className="text-xs" - variant="enabled" - text={formatNumber(data.remainingCredit)} - icon={<Coins size="sm-thin" />} - /> - </motion.div> - ) : ( - <motion.div - initial={{ opacity: 0, scale: 0.97 }} - animate={{ opacity: 1, scale: 1 }} - transition={{ - delay: 0.1, - duration: 0.2, - scale: { - type: "spring", - stiffness: 500, - damping: 25, - }, - }} - > - <StatusBadge - className="text-xs" - variant="disabled" - text="0" - icon={<Coins size="sm-thin" />} - /> - </motion.div> - )} + <CreditBadge remainingCredit={data.remainingCredit} />apps/dashboard/lib/trpc/routers/key/fetch-key-permissions.tsx (2)
45-78: Consider adding a comment explaining the complex query structureThe database query is complex with multiple nested relations. Consider adding a comment explaining the structure and purpose of each relation to improve maintainability.
+ // Query the key with all its related permissions data: + // - keyAuth: The keyspace this key belongs to + // - roles: The roles assigned to this key, including their permissions + // - permissions: Direct permissions assigned to this key + // - workspace: The workspace context including roles and permissions const key = await db.query.keys.findFirst({ where: and(eq(schema.keys.id, input.keyId), isNull(schema.keys.deletedAtM)), with: { keyAuth: true, roles: { with: { role: { with: { permissions: { with: { permission: true, }, }, }, }, }, }, permissions: true, workspace: { with: { roles: { with: { permissions: true, }, }, permissions: { with: { roles: true, }, }, }, }, }, });
100-109: Improve error logging with structured error informationThe current error logging only outputs a generic message. Consider adding structured error information to help with debugging.
if (error instanceof TRPCError) { throw error; } - console.error("Error retrieving key permissions:", error); + console.error("Error retrieving key permissions:", { + error, + keyId: input.keyId, + keyspaceId: input.keyspaceId, + workspaceId: ctx.workspace.id, + timestamp: new Date().toISOString(), + }); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Failed to retrieve key permissions. If this issue persists, please contact support@unkey.dev with the time this occurred.", });apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (2)
97-127: Consider refactoring switch statement to reduce complexityThe switch statement handling different filter types has some repeated logic. Consider refactoring to improve maintainability.
- switch (filter.field) { - case "outcomes": - if (typeof filter.value === "string") { - outcomeFilters.push({ - value: filter.value, - operator: "is", - }); - } - break; - - case "startTime": - case "endTime": { - const numValue = - typeof filter.value === "number" - ? filter.value - : typeof filter.value === "string" - ? Number(filter.value) - : Number.NaN; - - if (!Number.isNaN(numValue)) { - newParams[filter.field] = numValue; - } - break; - } - - case "since": - if (typeof filter.value === "string") { - newParams.since = filter.value; - } - break; - } + // Handle different filter types + const handlers = { + outcomes: (filter: KeyDetailsFilterValue) => { + if (typeof filter.value === "string") { + outcomeFilters.push({ + value: filter.value, + operator: "is", + }); + } + }, + startTime: (filter: KeyDetailsFilterValue) => { + const numValue = parseNumberValue(filter.value); + if (!Number.isNaN(numValue)) { + newParams[filter.field as "startTime" | "endTime"] = numValue; + } + }, + endTime: (filter: KeyDetailsFilterValue) => { + const numValue = parseNumberValue(filter.value); + if (!Number.isNaN(numValue)) { + newParams[filter.field as "startTime" | "endTime"] = numValue; + } + }, + since: (filter: KeyDetailsFilterValue) => { + if (typeof filter.value === "string") { + newParams.since = filter.value; + } + }, + }; + + // Call the appropriate handler or do nothing if field not found + const handler = handlers[filter.field as keyof typeof handlers]; + if (handler) { + handler(filter); + } // Add this helper function outside the component: +const parseNumberValue = (value: string | number): number => { + if (typeof value === "number") return value; + if (typeof value === "string") return Number(value); + return Number.NaN; +};
29-73: Consider adding a comment explaining the filter structure transformationThe
filtersuseMemo logic transforms URL parameters into a structured filter format. Consider adding a comment explaining this transformation for future developers.const filters = useMemo(() => { + // Transform URL search parameters into structured filter objects with IDs and metadata. + // Each filter gets a unique ID and appropriate metadata based on its type. const activeFilters: KeyDetailsFilterValue[] = []; for (const [field, value] of Object.entries(searchParams)) { if (!Array.isArray(value) || field !== "outcomes") { continue; }apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/utils.ts (1)
40-42: Consider adding a mapping function for outcome keysThe code manually converts outcome strings to lowercase for use as keys, but this approach may be brittle if outcome formats change.
Consider extracting this transformation to a dedicated function for better maintainability:
+/** + * Normalize outcome string to a consistent key format for charts + */ +function normalizeOutcomeKey(outcome: string): string { + return outcome.toLowerCase(); +} // ... - // Convert to the format used in our timeseries data (snake_case) - const key = outcome.toLowerCase(); + const key = normalizeOutcomeKey(outcome); const colorClass = OUTCOME_BACKGROUND_COLORS[outcome] || "bg-accent-4";apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-datetime/index.tsx (3)
13-28: Potential unnecessary re-render with default title settingSetting the default title inside
useEffectcan cause an additional render. Consider setting it directly in the initial state.- const [title, setTitle] = useState<string | null>(null); + const [title, setTitle] = useState<string>("Last 12 hours"); const { filters, updateFilters } = useFilters(); - useEffect(() => { - if (!title) { - setTitle("Last 12 hours"); - } - }, [title]);
19-28: Simplify filter extraction logicThe current reduction approach to extract time values is more complex than necessary.
- const timeValues = filters - .filter((f) => ["startTime", "endTime", "since"].includes(f.field)) - .reduce( - (acc, f) => ({ - // biome-ignore lint/performance/noAccumulatingSpread: it's safe to spread - ...acc, - [f.field]: f.value, - }), - {}, - ); + const timeValues = { + startTime: filters.find(f => f.field === "startTime")?.value, + endTime: filters.find(f => f.field === "endTime")?.value, + since: filters.find(f => f.field === "since")?.value, + };
70-72: Document the reason for filtering out minute optionsThe code filters out time options ending with "m" (minutes) without explaining why.
Add a comment explaining the rationale for excluding minute-based filters:
customOptions={DEFAULT_OPTIONS.filter( + // Filter out minute-based options to simplify the dropdown and focus on larger time windows (option) => !option.value || !option.value.endsWith("m"), )}apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/logs-table.tsx (2)
141-176: Deduplicate outcome-to-style logic – DRY violation
getStatusType,categorizeSeverity, and a bespokeSTATUS_STYLESobject copy nearly the same rules already exported fromutils/get-row-class.ts. Duplications risk divergence (note: this file uses “blocked” severity while the util module uses “moderate”).Prefer importing the shared helpers and styles instead of redefining them locally.
-import { STATUS_STYLES, categorizeSeverity } from "./utils/get-row-class"; -// remove local STATUS_STYLES, getStatusType & categorizeSeverity definitions +import { + STATUS_STYLES, + categorizeSeverity, +} from "../../utils/get-row-class";
374-405: Ensure unique keys for overflow-tags listSame duplication issue as above – use the provided
idxor a UUID instead ofidx + tag, which can still collide when two tags share the same prefix.- {log.tags.slice(3).map((tag, idx) => ( - <div key={idx + tag} className="text-xs"> + {log.tags.slice(3).map((tag, idx) => ( + <div key={`${idx}-${tag}`} className="text-xs">apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/utils/get-row-class.ts (2)
59-79: Potential mis-alignment of severity levels across componentsThis util defines
warning,moderate, anderror, whereas the logs table defineswarning,blocked, anderror.
Ensure both consumers reference the same taxonomy to avoid inconsistent colouring between overview and detail views. Consider exporting a canonical enum (e.g.Severity = { success, warning, blocked, error }) and deriving styles once.No immediate bug, but worth harmonising early.
114-129: Minor: selected-row opacity toggling could be simplifiedThe
cncall nests an object whose truthiness depends onselectedLog. You can inline the conditionals to avoid object allocations inside large tables:- selectedLog && { - "opacity-50 z-0": !isSelected, - "opacity-100 z-10": isSelected, - }, + !isSelected && selectedLog ? "opacity-50 z-0" : "", + isSelected ? "opacity-100 z-10" : "",Purely cosmetic; feel free to ignore if you prefer the existing pattern.
apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx (1)
56-64: Surface backend failure to caller instead of swallowing itWhen
enabledisfalse, the TRPC query never starts, yeterrorwill stayundefined.
Down-stream UI sees “no error → show spinner forever”.
Consider forcingstaleTime: Infinity+ conditional rendering, or explicitly short-circuit:const queryOptions = { enabled: shouldFetchPermissions, staleTime: 5 * 60_000 }; const { data, ... } = trpc.key.fetchPermissions.useQuery(params, queryOptions);Makes loading UX predictable.
internal/clickhouse/src/index.ts (2)
5-12: Inconsistent naming (MinutevsMinutely) will trip consumers & grep tools
getFiveMinutelyActiveKeysTimeseriesvsgetFiveMinuteRatelimitTimeseriesvsgetFiveMinuteLogsTimeseries.
Similarly in verifications you usegetFiveMinutelyVerificationTimeseries.Adopt one convention (
Minuteis more common) across all helpers & public keys:-import { getFiveMinutelyActiveKeysTimeseries } from "./keys/active_keys"; +import { getFiveMinuteActiveKeysTimeseries } from "./keys/active_keys";…and update re-exports & consumers.
Homogenous naming:
- keeps the API predictable
- avoids subtle “cannot find name” errors when refactoring
- makes auto-import work reliably
115-149: Public API expanding rapidly – consider a builder/helper to avoid copy-pasteThe
verifications.timeseriesandactiveKeysTimeseriesobjects repeat identical shape with only the factory function changing.A small factory can cut 50+ LOC and guarantee parity:
function timeseries(querier: Querier, fns: Record<string, Function>) { return Object.fromEntries( Object.entries(fns).map(([k, fn]) => [k, fn(querier)]), ) as Record<keyof typeof fns, ReturnType<(typeof fns)[string]>>; }timeseries: timeseries(this.querier, verificationFns), activeKeysTimeseries: timeseries(this.querier, activeKeyFns),Reduces drift risk when new granularities are added.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/hooks/use-logs-query.ts (1)
159-166: Trim real-time map in O(1) instead of O(n²)
Array.from(newMap.entries())+reduceis executed each time a new log arrives, giving quadratic behaviour as the map grows.
MaintainoldestKey(or use a min-heap) to evict in constant time:- if (newMap.size > Math.min(limit, REALTIME_DATA_LIMIT)) { - const entries = Array.from(newMap.entries()); - const oldestEntry = entries.reduce((oldest, current) => { - return oldest[1].time < current[1].time ? oldest : current; - }); - newMap.delete(oldestEntry[0]); - } + if (newMap.size > Math.min(limit, REALTIME_DATA_LIMIT)) { + // since combined size is small, `Math.min` is safe; just find min once + let oldestKey: string | undefined; + let oldestTime = Infinity; + for (const [id, l] of newMap) { + if (l.time < oldestTime) { + oldestTime = l.time; + oldestKey = id; + } + } + if (oldestKey) newMap.delete(oldestKey); + }apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts (1)
27-33:as constafter object literal is unnecessary
keyDetailsFilterFieldConfig.outcomesis already typed by the surrounding declaration;as consthere freezes all sub-properties and can make later extension cumbersome (e.g. adding validation).
Unless immutability is required, consider removing the assertion.internal/clickhouse/src/verifications.ts (2)
87-98: Expose next-page cursor to make the pagination API self-contained
cursorTimeis accepted but never emitted in the response, forcing callers to peek atlogs[logs.length-1].time.
Explicitly returningnextCursorTime(orhasMore) makes the contract clearer and decouples consumers from internal ordering.Example patch (after the previous fix):
return { - logs: clickhouseResults.val ?? [], - totalCount: totalResults.val?.[0]?.total_count ?? 0, + logs, + totalCount, + nextCursorTime: logs.length ? logs[logs.length - 1].time : null, };
532-541: Nit: consider clearer naming for minute helpers
getFiveMinutelyVerificationTimeseries/getThirtyMinutely...read awkwardly.
getFiveMinuteVerificationTimeseriesand friends are more natural and match the existing “TwoHourly”, “ThreeDay” pattern.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (71)
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/controls/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx(2 hunks)apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/actions.ts(2 hunks)apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx(2 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/hooks/use-fetch-timeseries.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/query-timeseries.schema.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/utils.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/control-cloud/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-datetime/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-filters/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-filters/outcome-filter.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-live-switch.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-refresh.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/rbac/permissions.tsx(6 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/log-details/components/hooks/use-logs-query.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/log-details/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/status-badge.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/hooks/use-logs-query.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/logs-table.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/query-logs.schema.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/utils/calculate-blocked-percentage.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/utils/get-row-class.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/context/logs.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/logs-client.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/delete-key.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/navigation.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-enabled.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-expiration.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-metadata.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-name.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-owner-id.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-ratelimit.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/verification-table.tsx(0 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/controls/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-credits/index.tsx(3 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/keys-table-action.popover.tsx(2 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/page.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/settings/components/status-badge.tsx(0 hunks)apps/dashboard/app/(app)/apis/loading.tsx(0 hunks)apps/dashboard/app/(app)/logs/components/table/log-details/components/log-meta.tsx(1 hunks)apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/components/override-indicator.tsx(1 hunks)apps/dashboard/components/logs/validation/filter.types.ts(1 hunks)apps/dashboard/components/ui/badge.tsx(1 hunks)apps/dashboard/components/virtual-table/index.tsx(3 hunks)apps/dashboard/components/virtual-table/types.ts(1 hunks)apps/dashboard/lib/trpc/routers/api/keys/timeseries.utils.ts(1 hunks)apps/dashboard/lib/trpc/routers/index.ts(2 hunks)apps/dashboard/lib/trpc/routers/key/fetch-key-permissions.tsx(1 hunks)apps/dashboard/lib/trpc/routers/key/query-logs/index.ts(1 hunks)apps/dashboard/lib/trpc/routers/key/query-logs/utils.ts(1 hunks)apps/dashboard/lib/trpc/routers/key/query-timeseries/index.ts(1 hunks)apps/dashboard/lib/trpc/routers/utils/granularity.ts(2 hunks)internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql(1 hunks)internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql(1 hunks)internal/clickhouse/src/index.ts(4 hunks)internal/clickhouse/src/keys/active_keys.ts(8 hunks)internal/clickhouse/src/verifications.ts(7 hunks)internal/icons/src/icons/coins.tsx(1 hunks)internal/icons/src/icons/time-clock.tsx(1 hunks)internal/icons/src/index.ts(1 hunks)
💤 Files with no reviewable changes (13)
- apps/dashboard/app/(app)/apis/[apiId]/settings/components/status-badge.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-owner-id.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx
- apps/dashboard/app/(app)/apis/loading.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/delete-key.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-ratelimit.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-metadata.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/verification-table.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/navigation.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-enabled.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-name.tsx
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-expiration.tsx
🧰 Additional context used
🧬 Code Graph Analysis (22)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-credits/index.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
trpc(7-14)
internal/icons/src/icons/time-clock.tsx (1)
internal/icons/src/props.ts (2)
IconProps(30-35)sizeMap(7-28)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/keys-table-action.popover.tsx (2)
apps/dashboard/lib/utils.ts (1)
cn(5-7)internal/icons/src/icons/dots.tsx (1)
Dots(15-60)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx (2)
apps/dashboard/lib/utils.ts (1)
cn(5-7)internal/ui/src/components/button.tsx (1)
AnimatedLoadingSpinner(253-312)
apps/dashboard/lib/trpc/routers/utils/granularity.ts (1)
apps/dashboard/lib/trpc/routers/utils/constants.ts (2)
DAY_IN_MS(2-2)HOUR_IN_MS(1-1)
apps/dashboard/lib/trpc/routers/api/keys/timeseries.utils.ts (1)
internal/clickhouse/src/verifications.ts (1)
VerificationTimeseriesParams(211-211)
apps/dashboard/lib/trpc/routers/index.ts (4)
apps/dashboard/lib/trpc/routers/key/fetch-key-permissions.tsx (1)
fetchKeyPermissions(25-111)apps/dashboard/lib/trpc/trpc.ts (1)
t(7-7)apps/dashboard/lib/trpc/routers/key/query-logs/index.ts (1)
queryKeyDetailsLogs(18-79)apps/dashboard/lib/trpc/routers/key/query-timeseries/index.ts (1)
keyDetailsVerificationsTimeseries(8-66)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx (4)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (1)
useFilters(24-150)apps/dashboard/lib/trpc/server.ts (1)
trpc(7-14)apps/dashboard/components/logs/validation/utils/transform-structured-output-filter-format.ts (1)
transformStructuredOutputToFilters(18-61)apps/dashboard/components/logs/llm-search/index.tsx (1)
LogsLLMSearch(25-171)
internal/icons/src/icons/coins.tsx (1)
internal/icons/src/props.ts (2)
IconProps(30-35)sizeMap(7-28)
apps/dashboard/lib/trpc/routers/key/query-logs/utils.ts (3)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/query-logs.schema.ts (1)
KeyDetailsLogsPayload(23-23)internal/clickhouse/src/verifications.ts (1)
KeyDetailsLogsParams(59-59)apps/dashboard/lib/utils.ts (1)
getTimestampFromRelative(133-158)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (2)
apps/dashboard/components/logs/validation/utils/nuqs-parsers.ts (2)
parseAsFilterValueArray(29-57)parseAsRelativeTime(5-25)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts (5)
KeyDetailsFilterValue(64-64)KeyDetailsFilterField(45-45)keyDetailsFilterFieldConfig(14-33)KeysQuerySearchParams(66-71)IsOnlyUrlValue(54-57)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/status-badge.tsx (1)
apps/dashboard/lib/utils.ts (1)
cn(5-7)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-filters/index.tsx (5)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (1)
useFilters(24-150)apps/dashboard/components/logs/checkbox/filters-popover.tsx (1)
FiltersPopover(57-236)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-filters/outcome-filter.tsx (1)
OutcomesFilter(10-38)internal/ui/src/components/button.tsx (1)
Button(473-473)internal/icons/src/icons/bars-filter.tsx (1)
BarsFilter(15-63)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/query-logs.schema.ts (1)
internal/clickhouse/src/keys/keys.ts (1)
KEY_VERIFICATION_OUTCOMES(4-13)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/utils.ts (3)
apps/dashboard/app/(app)/apis/[apiId]/_overview/utils.ts (1)
formatOutcomeName(12-20)internal/clickhouse/src/keys/keys.ts (1)
KEY_VERIFICATION_OUTCOMES(4-13)apps/dashboard/app/(app)/apis/[apiId]/_overview/constants.ts (1)
OUTCOME_BACKGROUND_COLORS(11-20)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/query-timeseries.schema.ts (1)
internal/clickhouse/src/keys/keys.ts (1)
KEY_VERIFICATION_OUTCOMES(4-13)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/utils/calculate-blocked-percentage.ts (2)
internal/clickhouse/src/logs.ts (1)
log(24-38)internal/clickhouse/src/keys/keys.ts (1)
KeysOverviewLog(117-117)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/logs-client.tsx (7)
internal/clickhouse/src/verifications.ts (1)
KeyDetailsLog(58-58)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/context/logs.tsx (1)
KeyDetailsLogsProvider(12-29)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/index.tsx (1)
KeysDetailsLogsControls(17-113)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/control-cloud/index.tsx (1)
KeysDetailsLogsControlCloud(20-31)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/index.tsx (1)
KeyDetailsLogsChart(7-78)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/logs-table.tsx (1)
KeyDetailsLogsTable(185-486)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/log-details/index.tsx (1)
KeyDetailsDrawer(28-114)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts (4)
internal/clickhouse/src/keys/keys.ts (1)
KEY_VERIFICATION_OUTCOMES(4-13)apps/dashboard/app/(app)/apis/[apiId]/_overview/utils.ts (1)
getOutcomeColor(25-30)apps/dashboard/components/logs/validation/utils/structured-output-schema-generator.ts (1)
createFilterOutputSchema(5-39)apps/dashboard/components/logs/validation/filter.types.ts (3)
NumberConfig(14-18)StringConfig(33-38)FilterValue(49-62)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-datetime/index.tsx (4)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (1)
useFilters(24-150)apps/dashboard/components/logs/datetime/datetime-popover.tsx (1)
DatetimePopover(35-306)apps/dashboard/components/logs/datetime/constants.ts (1)
DEFAULT_OPTIONS(3-94)apps/dashboard/lib/utils.ts (1)
cn(5-7)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/hooks/use-logs-query.ts (6)
internal/clickhouse/src/verifications.ts (1)
KeyDetailsLog(58-58)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (1)
useFilters(24-150)apps/dashboard/providers/query-time-provider.tsx (1)
useQueryTime(74-80)apps/dashboard/components/logs/constants.ts (1)
HISTORICAL_DATA_WINDOW(2-2)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts (1)
keyDetailsFilterFieldConfig(14-33)internal/clickhouse/src/keys/keys.ts (1)
KEY_VERIFICATION_OUTCOMES(4-13)
internal/clickhouse/src/verifications.ts (1)
internal/clickhouse/src/keys/keys.ts (1)
KEY_VERIFICATION_OUTCOMES(4-13)
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: Test Go API Local / Test (Shard 2/8)
- GitHub Check: Test Go API Local / Test (Shard 4/8)
- GitHub Check: Test Go API Local / Test (Shard 5/8)
- GitHub Check: Test Go API Local / Test (Shard 3/8)
- GitHub Check: Test Go API Local / Test (Shard 1/8)
- GitHub Check: Test API / API Test Local
🔇 Additional comments (77)
internal/icons/src/icons/time-clock.tsx (3)
13-16: Good refactoring to support dynamic sizingThe TimeClock component now properly accepts a size prop with a default value, making it more flexible and consistent with other icon components. The implementation correctly leverages the sizeMap to determine pixel size and stroke width dynamically.
19-25: SVG attributes properly updated for dynamic sizingThe SVG element correctly spreads remaining props and uses the dynamic pixelSize values for height and width, maintaining the viewBox constraints for visual consistency.
32-32: Stroke width dynamically applied to all pathsAll path elements have been properly updated to use the dynamic strokeWidth from sizeMap instead of hardcoded values, ensuring consistent visual appearance across different size variants.
Also applies to: 39-39, 46-46, 53-53, 60-60, 67-67
internal/icons/src/index.ts (1)
98-98: New Coins icon export added correctlyThe new Coins icon export follows the established pattern and alphabetical ordering in the index file.
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/components/override-indicator.tsx (1)
30-30: Text size reduced for better tooltip appearanceThe text size in the tooltip has been reduced from
text-smtotext-xs, which likely improves the tooltip's appearance by making it more compact and consistent with other tooltips in the UI.apps/dashboard/app/(app)/logs/components/table/log-details/components/log-meta.tsx (1)
24-24: Improved empty content handlingGood addition of a fallback message when content is null or undefined. This enhances the user experience by displaying "" instead of an empty space, making it clear to users when no content is available.
apps/dashboard/app/(app)/apis/[apiId]/page.tsx (1)
3-3: Improved performance with dynamic component loadingConverting
ApisNavbarto a dynamic import is a good optimization that enables code splitting. This will reduce the initial JavaScript bundle size and improve page load performance.Also applies to: 5-7
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx (1)
95-97: Enhanced visual feedback for loading stateThe loading spinner now has contextual coloring based on identity presence. This improves the UX by maintaining visual consistency during navigation states.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx (1)
1-1: Consistent dynamic loading pattern appliedUsing dynamic imports for
ApisNavbarbrings this component in line with the pattern established elsewhere, improving code consistency and page performance.Also applies to: 5-7
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-credits/index.tsx (3)
9-9: Good addition of TRPC client import.The import of
trpcclient utility is necessary for cache invalidation after successful key updates.
25-25: LGTM: Proper use of TRPC utilities.Correctly obtaining the TRPC utilities object for cache invalidation operations.
57-57: Cache invalidation improves UI consistency.Invalidating the permissions cache after editing credits ensures that any components displaying permissions data are refreshed with the latest information.
apps/dashboard/components/virtual-table/types.ts (1)
68-75: Great addition of row mouse event handlers.The new mouse event handlers with proper JSDoc comments enhance the interactivity capabilities of the VirtualTable component. These callbacks will allow for rich hover interactions like tooltips or contextual highlighting.
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx (1)
39-41: Good UX improvement for navigation state management.Adding the
onNavigatecallback that clears the selected log ensures a clean state when navigating to a new key, preventing stale selections and potential UI inconsistencies.apps/dashboard/components/ui/badge.tsx (2)
9-15: Excellent enhancement of badge variants.The expanded set of badge variants improves UI consistency and adds semantic meaning with new status-specific styles. The consistent pattern for background/text colors and hover states makes the code maintainable.
18-18: Improved small badge padding.The slight increase in padding for small badges will improve readability and touch targets.
apps/dashboard/lib/trpc/routers/api/keys/timeseries.utils.ts (1)
9-10: Type enhancement improves precision.The addition of "apiId" to the list of omitted fields in the return type ensures more accurate type safety. This change prevents the apiId field from being incorrectly included in downstream operations, aligning with the intended usage pattern in the key details verification timeseries queries.
apps/dashboard/lib/trpc/routers/index.ts (2)
30-32: LGTM: Clean module imports for new API functionality.These imports bring in the necessary procedures for the new key details features.
93-97: Well-structured API extension for key management.The new endpoints are properly organized under the existing key router, maintaining a consistent API structure. These additions provide valuable new capabilities for fetching permissions and log data at both individual and timeseries levels.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-live-switch.tsx (1)
1-17: Clean implementation of the live toggle functionality.The component follows React best practices with proper hooks usage and a focused responsibility. The refresh logic when switching from live mode is particularly valuable for ensuring data consistency.
apps/dashboard/app/(app)/apis/[apiId]/actions.ts (1)
3-4: LGTM: Appropriate imports for the new functionality.The imported functions and types support the new key details retrieval capability.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/keys-table-action.popover.tsx (2)
27-31: Excellent enhancement to component flexibility!Adding the
childrenprop toKeysTableActionPopoverincreases reusability by allowing customization of the trigger element. This is a good pattern for composable components.
54-66: Well-implemented conditional rendering.The implementation properly handles both scenarios: custom trigger element when children are provided, and fallback to the default button with Dots icon. The styling is consistent and accessible.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/controls/index.tsx (1)
1-14: Good refactoring for consistency and enhanced filtering.The refactoring improves component structure by using reusable layout components (
ControlsContainerandControlsLeft) and adds date-time filtering capability withLogsDateTime. This aligns with the broader pattern of control components refactoring in the dashboard.apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/log-details/components/hooks/use-logs-query.ts (2)
8-37: Well-structured data fetching hook.The hook follows best practices by:
- Using conditional query execution with
enabled: Boolean(requestId)- Disabling automatic refetching behavior for performance
- Setting appropriate time range (0 to current timestamp)
- Using proper filter structure for requestId
38-43: Clean API surface with minimal return values.The hook returns only the necessary data and states: the first matching log, loading state, and error state. This makes the hook easy to use and understand.
apps/dashboard/components/virtual-table/index.tsx (2)
58-59: Good addition of mouse event handlers to table props.Adding
onRowMouseEnterandonRowMouseLeavecallbacks enhances interactivity for table rows, likely supporting features like hover tooltips or visual feedback in the key details logs UI.
266-267: Consistent implementation across layout modes.The mouse event handlers are correctly implemented in both grid and classic layout modes, ensuring consistent behavior. Using optional chaining (
?.) is appropriate for callback props that might be undefined.Also applies to: 334-335
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-refresh.tsx (1)
1-20: Well-structured refresh component with good conditional enablementThis component is well-implemented, properly integrating with the application's filtering and query time context. I like how the refresh button is conditionally enabled based on the presence of a relative time filter, which is a good UX decision since refreshing makes sense only in that context.
The invalidation of all three relevant queries ensures that all key-related data is updated consistently.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/context/logs.tsx (1)
5-29: LGTM - Well-structured context implementationThe context pattern is implemented correctly with proper typing and a flexible toggle function that allows both toggling and explicit setting of the live state.
internal/clickhouse/schema/051_create_verifications.key_verifications_per_minute_mv_v1.sql (2)
1-24: Well-designed materialized view for minute-level aggregationThe materialized view correctly aggregates verification data at a minute granularity with proper grouping fields and timestamp handling. This will significantly improve query performance for time-series analytics by pre-computing the aggregated results.
25-26: LGTM - Proper down migration providedThe down migration correctly drops the view, making this change fully reversible.
apps/dashboard/lib/trpc/routers/key/query-logs/utils.ts (1)
5-33: Well-implemented transformation utilityThis utility function cleanly handles both explicit and relative time filtering, properly transforms outcome filters, and builds a structured parameter object for the underlying query system. The time handling is particularly good, converting relative "since" values when present or using explicit timestamps otherwise.
internal/icons/src/icons/coins.tsx (3)
1-11: License information properly includedThe component includes required license information from Nucleo, which clearly specifies usage restrictions. This is important to maintain compliance with the licensing terms.
15-17: Well-structured component with proper propsThe component correctly implements the IconProps interface with a sensible default size of "xl-thin", and properly extracts the pixel size and stroke width from the sizeMap.
18-129: Well-implemented SVG icon with dynamic sizingThe SVG implementation follows best practices:
- Uses currentColor for both fill and stroke to inherit colors from parent components
- Properly applies dynamic strokeWidth based on the size prop
- Maintains consistent line caps and joins for visual consistency
- Uses semantic grouping with the
<g>elementThe icon will integrate well with the existing icon system based on the provided context from the props.ts file.
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/controls/index.tsx (1)
1-5: Improved component structure with semantic containersThis refactoring improves the code by replacing manually constructed flexbox layouts with more semantic and reusable container components. This change enhances:
- Code readability by reducing nesting
- Maintainability through consistent component patterns
- Reusability across different parts of the application
The component hierarchy is now much cleaner while preserving the same functionality.
Also applies to: 13-22
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/rbac/permissions.tsx (4)
19-24: Improved data management with targeted cache invalidationReplacing page refreshes with targeted cache invalidation is a significant improvement. This approach:
- Reduces unnecessary re-renders
- Improves performance by only refreshing relevant data
- Provides a better user experience with less visual disruption
The extracted
invalidatePermissionsfunction also improves code reusability.
32-34: Consistent cache invalidation after mutationsProperly invalidating the permissions cache after both connecting and disconnecting roles ensures the UI stays in sync with the backend state without full page refreshes.
Also applies to: 48-50
58-58: UI improvements for better user experienceSeveral UI improvements enhance the user experience:
- Setting the card to full height (
h-full) improves layout consistency- Uniform padding (
p-4) provides better visual balance- Disabling checkboxes during loading states prevents multiple submissions
These changes create a more polished and responsive interface.
Also applies to: 65-65, 74-74
88-92: Added empty state handlingGood addition of a "No roles available" message when the roles array is empty. This provides clear feedback to users rather than showing an empty space, improving the overall user experience.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/query-logs.schema.ts (3)
1-3: Appropriate imports for schema definitionThe imports are properly structured, bringing in the necessary Zod library for schema validation and the KEY_VERIFICATION_OUTCOMES enum from the relevant module.
4-21: Well-structured schema with appropriate validationsThe schema definition is comprehensive and includes proper typing for all fields:
- Integer validations for numeric fields
- String validations for ID fields
- Proper handling of optional and nullable fields
- Strongly typed enum validation for outcomes using the imported constants
The schema structure appears well-aligned with the querying needs for key verification logs.
23-23: Type export provides good developer experienceExporting the TypeScript type inferred from the Zod schema provides a consistent type definition that can be used throughout the application, ensuring type safety when working with this payload structure.
apps/dashboard/lib/trpc/routers/utils/granularity.ts (1)
55-55: Good addition of optional parameter with sensible default value.The new
allowMinutelyForVerificationsparameter with a default oftruemaintains backward compatibility while enabling more granular control over the time series resolution.apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx (1)
1-6: Well-structured imports that follow project conventions.The imports are properly organized with relevant components and utilities.
internal/clickhouse/src/keys/active_keys.ts (5)
63-78: Well-structured minute-based interval definitions.The addition of minute-based intervals follows the established pattern in the codebase, making it easy to understand. Good use of comments to clearly distinguish between different time scale groups.
All the intervals properly reference the same table with appropriate step definitions.
141-143: Proper extension of interval unit mapping.Correctly added the minute-related interval units to the mapping object, maintaining consistency with existing implementations.
153-154: Consistent millisecond conversions for new interval types.The millisecond per unit calculations are correctly defined for the new minute intervals, ensuring accurate time step calculations in queries.
290-299: Clean implementation of minute-based time series exporters.The new minute-based time series exporters follow the same pattern as the existing exporters, promoting consistency and maintainability.
Good use of comments to organize the exporters by time scale, making the code more navigable.
301-301: Improved code organization with categorizing comments.The addition of categorical comments (Hour-based, Day-based, Month-based) improves code readability by visually separating different groups of related functions.
Also applies to: 317-319, 331-332
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-filters/outcome-filter.tsx (3)
1-4: Clean imports with proper path management.The component imports dependencies with clear and concise paths, following project conventions.
5-9: Well-documented component props.The component props include helpful comments that explain the purpose of the
onDrawerClosecallback, making it easier for other developers to use the component correctly.
10-38: Effective filter component implementation with reusable patterns.This component demonstrates good practices:
- Centralized options management via
getOutcomeOptions()- Clean integration with the filter system through
useFiltershook- Proper visualization of filter options with color coding
- Consistent metadata handling for filter values
The component is concise yet complete, handling all necessary filter operations.
apps/dashboard/lib/trpc/routers/key/query-timeseries/index.ts (4)
1-6: All imports look clean and appropriate.The imports are well-organized, bringing in the necessary components for schema validation, database access, authentication, rate limiting, and error handling.
8-12: Good middleware composition for the TRPC procedure.The procedure correctly applies user authentication, workspace context, and rate limiting for read operations in the proper order.
41-46: Proper error handling for missing workspace.The code correctly checks if the workspace exists and throws an appropriate error with a helpful message.
62-66: Clean and concise return structure.The response format is clean, returning both the timeseries data and the granularity used for the query.
internal/clickhouse/schema/050_create_verifications.key_verifications_per_minute_v1.sql (3)
4-11: Good schema design for time-series aggregation.The table structure is well-designed for time-series aggregation:
- Using
LowCardinality(String)for outcome is an excellent optimization for repeated string values- Array type for tags provides flexibility
- Including all necessary dimension fields for proper filtering and aggregation
13-14: Appropriate engine choice for aggregation use case.Using
SummingMergeTreewith ordering by all dimension fields is perfect for this aggregation table since it automatically sums the count during merges.
18-19: Clean down migration.The down migration correctly drops the table for rollback scenarios.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/utils/calculate-blocked-percentage.ts (2)
3-6: Well-documented type for error severity.The ErrorSeverity type definition is clear and well-documented with a helpful JSDoc comment.
24-37: Function for success percentage is well-implemented.The getSuccessPercentage function correctly handles the edge case of zero total requests and calculates the percentage properly.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/index.tsx (3)
1-6: Imports look appropriate.The imports are clean and focused on the required dependencies for this component.
7-24: Good component structure with appropriate hooks.The component is well-structured with clear props and effective use of custom hooks for filtering and data fetching.
57-78: Clean and concise component rendering.The OverviewBarChart component is well-configured with appropriate props and labels. The layout with responsive height using Tailwind CSS is a good approach.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/query-timeseries.schema.ts (1)
5-21: LGTM! Well-structured schema for timeseries queriesThe schema properly defines required fields with appropriate types and validation rules. The nullable outcomes filter with enum validation ensures type safety when filtering by verification outcomes.
apps/dashboard/lib/trpc/routers/key/fetch-key-permissions.tsx (1)
25-31: LGTM! Well-structured TRPC procedure with proper middlewareThe procedure correctly uses authentication, workspace context, and rate limiting middleware, with proper input and output validation.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts (1)
15-15: LGTM! Good use of custom parser for filter valuesThe custom parser is well-implemented to handle the specific "is" operator constraint required by the application.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/bar-chart/utils.ts (1)
5-14: Good implementation of CSS variable conversionThe
cssVarToChartColorfunction elegantly handles the conversion from design system CSS variables to chart-compatible HSL strings. The removal of the "bg-" prefix is a nice touch to support both raw variable names and class names.apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx (2)
67-93: Great implementation of loading state with visual feedbackThe component provides excellent user feedback by showing a loading spinner during navigation. The transition between warning icon and spinner is smooth, and the animation helps indicate that an action is in progress.
11-15: Good API design with optional callbackAdding the optional
onNavigatecallback allows parent components to respond to navigation events, improving component reusability and composition.apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-datetime/index.tsx (1)
29-67: Well-structured filter update logicThe filter update logic is well-structured and handles both relative and absolute time filtering appropriately. Good use of early returns and conditional logic to handle different scenarios.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/logs-client.tsx (2)
1-31: Good use of hooks and state managementThe component efficiently manages state with
useStateanduseCallbackhooks. The callbacks are properly memoized to prevent unnecessary re-renders.
33-36: Good component organizationThe component follows a clear hierarchical structure, with controls at the top, followed by data visualization and interaction components. This organization makes the UI intuitive for users.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx (1)
1-7: Duplicate dynamic import can be eagerly inlinedThis server component runs only on the server, but
dynamic()defaults to client-side chunking.
IfApisNavbarcontains no client-only code, importing it statically lets the server bundle tree-shake unused JS and removes an extra network request on the client.
If you do need dynamic + SSR, pass{ ssr: true }explicitly so that suspense boundaries work as expected.internal/clickhouse/src/verifications.ts (1)
220-235: Confirm ClickHouse accepts plural interval tokens
INTERVAL 5 MINUTES/30 MINUTESis generated for minute buckets.
Some ClickHouse versions only accept singular units (MINUTE,HOUR, …).
Please double-check compatibility; if plural forms are not universally supported, keepstepsingular and rely solely on the numericstepSize.
...app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/components/status-badge.tsx
Show resolved
Hide resolved
...)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts
Outdated
Show resolved
Hide resolved
* chore: remove unused components * chore: cleanup * chore: tooltip cleanup * chore: remove tooltips * chore: remove checkbox * chore: remove input and use ui input component * chore: fmt * chore: remove form component * chore: remove RSC loadings * chore: fmt
What does this PR do?
This PR adds new key details page at
https://app.unkey.com/apis/xxx/keys/xxxx/xxxx. Also removes some redundant CH queries and functions. Fixes some style inconsistencies between pages.Type of change
How should this be tested?
Checklist
Required
pnpm buildpnpm fmtconsole.logsgit pull origin mainAppreciated
Summary by CodeRabbit
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Refactor
Chores
Style
Tests