Skip to content

feat: key details#3242

Merged
ogzhanolguncu merged 31 commits intomainfrom
key-details
May 16, 2025
Merged

feat: key details#3242
ogzhanolguncu merged 31 commits intomainfrom
key-details

Conversation

@ogzhanolguncu
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu commented May 12, 2025

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

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Make sure key details page works
  • Make sure charts return correct minutely materialized views
  • Make sure /keys page works as before.
  • Make sure permissions at key details work properly
  • Make sure live query works for key details
  • Make sure request details drawer works for key details

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added a comprehensive API key logs interface with interactive charts, filters, LLM-powered search, and a virtualized logs table supporting real-time and historical data.
    • Introduced a detailed resizable drawer for viewing log request/response details and metadata.
    • Implemented granular verification timeseries analytics with new outcome-based filtering.
    • Integrated RBAC management and permission displays for API keys in the navbar.
    • Added new React context and hooks for managing live log state and filters synchronized with URL query parameters.
    • Added a new coins icon and updated time clock icon with dynamic sizing.
    • Introduced new controls for logs filtering including date-time, live switch, refresh, and outcome filters.
    • Added a new status badge component for consistent status display.
    • Added a new React hook to fetch detailed key information by ID.
  • Improvements

    • Enhanced navbar with dynamic breadcrumbs, API/key selection popovers, and permission dialogs.
    • Refined status badges and log outcome severity indicators with updated color schemes.
    • Improved table row styling, hover effects, and added mouse event callbacks for rows.
    • Added finer timeseries granularity options (minute, 5-minute, 30-minute, hour).
    • Simplified control layouts using reusable container components.
    • Converted static navbar imports to dynamic imports for better performance.
    • Added animated loading spinner during navigation in key lists.
    • Improved tooltip display logic with conditional loading spinner and font size adjustment.
    • Enhanced key list and logs controls layout for better semantic structure and responsiveness.
    • Added cache invalidation after permission and credit updates to ensure fresh data.
  • Bug Fixes

    • Displayed "" placeholder for empty log content in detail views.
    • Reduced tooltip font size for better readability.
  • Refactor

    • Replaced legacy verification aggregation queries with paginated log retrieval and enhanced timeseries.
    • Modularized logs controls and key management UI components.
    • Migrated static imports of navbar components to dynamic imports for performance.
    • Removed page refreshes after permission updates, replaced with targeted cache invalidation.
    • Simplified API key detail page to delegate rendering and data fetching to client components.
    • Refactored layout components to use semantic and reusable containers.
    • Updated icon components to support dynamic sizing and streamlined SVG attributes.
  • Chores

    • Removed legacy API key settings, update, and deletion components.
    • Updated type definitions for immutability and stricter validation.
    • Added explicit cache invalidation calls after key permission updates.
    • Improved type safety in filter configurations and query parameter parsing.
  • Style

    • Adjusted badge paddings and font sizes for visual consistency.
    • Updated spinner styling during navigation states.
    • Changed separator background color for visual refinement.
    • Updated badge variant colors and sizes for better UI consistency.
  • Tests

    • Updated test suites to align with new log retrieval methods and timeseries data structures.
    • Improved determinism and polling in verification outcome propagation tests.

@changeset-bot
Copy link

changeset-bot bot commented May 12, 2025

⚠️ No Changeset found

Latest commit: dc2337c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented May 12, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 2:46pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
engineering ⬜️ Ignored (Inspect) Visit Preview May 16, 2025 2:46pm

@github-actions
Copy link
Contributor

github-actions bot commented May 12, 2025

Thank you for following the naming conventions for pull request titles! 🙏

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 12, 2025

📝 Walkthrough

"""

Walkthrough

This 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

File(s)/Path(s) Change Summary
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx
apps/dashboard/app/(app)/apis/loading.tsx
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/*
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/verification-table.tsx
Replaced the complex server-rendered key detail page and all settings/verification components with a minimal client-driven page using new logs UI components. Deleted the loading placeholder and all settings-related files.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/logs-client.tsx
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/*
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/context/logs.tsx
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/hooks/use-filters.ts
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/filters.schema.ts
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/charts/*
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/*
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/*
Added new modular client components, hooks, context, and schemas for logs display, controls, filtering, charting, and table rendering for key details. Introduced supporting utilities and filter management logic.
apps/dashboard/app/(app)/apis/[apiId]/actions.ts Added getKeyDetails function to fetch detailed key info.
apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx Refactored navbar to support RBAC features, key permissions, and dynamic UI based on selected key.
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/controls/index.tsx
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/controls/index.tsx
Refactored controls layout to use semantic container components and added/updated controls.
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx
Updated table components for new navigation logic and enhanced tooltip/interactivity.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-credits/index.tsx Added TRPC cache invalidation after editing credits.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/keys-table-action.popover.tsx Updated popover to allow custom trigger via children prop.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx Adjusted spinner styling for navigation state.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx
apps/dashboard/app/(app)/apis/[apiId]/page.tsx
Changed ApisNavbar import to dynamic loading.
apps/dashboard/app/(app)/apis/[apiId]/settings/components/status-badge.tsx Removed trailing blank/comment lines.
apps/dashboard/app/(app)/logs/components/table/log-details/components/log-meta.tsx Added fallback display for empty content.
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/components/override-indicator.tsx Reduced tooltip font size.
apps/dashboard/components/logs/validation/filter.types.ts Made operators property of filter config type immutable.
apps/dashboard/components/ui/badge.tsx Revised badge variant styles and size padding.
apps/dashboard/components/virtual-table/index.tsx
apps/dashboard/components/virtual-table/types.ts
Added row mouse enter/leave callbacks to virtual table component and types.
apps/dashboard/lib/trpc/routers/api/keys/timeseries.utils.ts Adjusted return type of transformVerificationFilters to omit more keys.
apps/dashboard/lib/trpc/routers/index.ts Added key permissions and logs/timeseries procedures to TRPC router.
apps/dashboard/lib/trpc/routers/key/fetch-key-permissions.tsx Added new TRPC procedure for fetching key permissions.
apps/dashboard/lib/trpc/routers/key/query-logs/index.ts
apps/dashboard/lib/trpc/routers/key/query-logs/utils.ts
Added new TRPC procedure and utility for querying key detail logs.
apps/dashboard/lib/trpc/routers/key/query-timeseries/index.ts Added TRPC procedure for key verification timeseries data.
apps/dashboard/lib/trpc/routers/utils/granularity.ts Expanded granularity types and simplified selection logic.
internal/clickhouse/src/index.ts
internal/clickhouse/src/verifications.ts
Added new timeseries and log retrieval functions for keys, removed legacy aggregation methods, and introduced paginated log querying with outcome filtering.
internal/icons/src/icons/coins.tsx
internal/icons/src/icons/time-clock.tsx
internal/icons/src/index.ts
Added new Coins icon, updated TimeClock for dynamic sizing, and exported new icon.
internal/clickhouse/src/insert_verifications.test.ts
internal/clickhouse/src/verification_tags.test.ts
Updated tests to use new log/timeseries methods and improved materialized view testing logic.
apps/api/src/pkg/analytics.ts Updated analytics getter to use new timeseries path for daily verifications.
apps/api/src/routes/v1_keys_getVerifications.ts Refactored verifications data retrieval to use transformed timeseries data and updated parameter names.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/disable-key.tsx Added cache revalidation call after disabling key.

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
Loading

Possibly related PRs

  • unkeyed/unkey#2991: Related to adding apiId prop and navigation enhancements in key logs and controls components, overlapping with this PR's changes to key logs UI and navigation.
  • unkeyed/unkey#2804: Introduces the ControlCloud component and related filter management logic; related to this PR’s use of ControlCloud in key details logs controls.
  • unkeyed/unkey#2238: Enhances permissions visualization on the API key detail page; related but mutually exclusive with this PR’s refactor of the key details page UI.

Suggested labels

Feature, Dashboard

Suggested reviewers

  • perkinsjr
  • mcstepp
  • MichaelUnkey
  • chronark
    """
✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 29

🔭 Outside diff range comments (1)
internal/clickhouse/src/verifications.ts (1)

16-26: 🛠️ Refactor suggestion

⚠️ Potential issue

Unify outcome enum definition to avoid drift and include the empty-string case

insertVerification re-declares the list of outcome literals instead of re-using KEY_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 block

Remember to replicate the optimisation & null-guard refactor for the response body section to avoid the same undefined edge case.

🧹 Nitpick comments (38)
apps/dashboard/app/(app)/apis/[apiId]/actions.ts (1)

70-91: Well-designed utility function for key details retrieval.

The getKeyDetails function is efficiently implemented, leveraging the existing getAllKeys function 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 names

The 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 the since field

The since field 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 KeysDetailsLogsControlCloud component nicely integrates the ControlCloud UI component with the filter state management through the useFilters hook. The formatFieldName utility 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 isError state 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:

  1. Capturing the error message from the TRPC query
  2. Providing a fallback UI or retry mechanism
  3. 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 primary object 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 granularity being 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 constant

The MAX_KEYID_COUNT constant 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 constant

The 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 component

There'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 structure

The 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 information

The 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 complexity

The 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 transformation

The filters useMemo 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 keys

The 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 setting

Setting the default title inside useEffect can 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 logic

The 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 options

The 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 bespoke STATUS_STYLES object copy nearly the same rules already exported from utils/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 list

Same duplication issue as above – use the provided idx or a UUID instead of idx + 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 components

This util defines warning, moderate, and error, whereas the logs table defines warning, blocked, and error.
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 simplified

The cn call nests an object whose truthiness depends on selectedLog. 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 it

When enabled is false, the TRPC query never starts, yet error will stay undefined.
Down-stream UI sees “no error → show spinner forever”.
Consider forcing staleTime: 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 (Minute vs Minutely) will trip consumers & grep tools

getFiveMinutelyActiveKeysTimeseries vs getFiveMinuteRatelimitTimeseries vs getFiveMinuteLogsTimeseries.
Similarly in verifications you use getFiveMinutelyVerificationTimeseries.

Adopt one convention (Minute is 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-paste

The verifications.timeseries and activeKeysTimeseries objects 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()) + reduce is executed each time a new log arrives, giving quadratic behaviour as the map grows.
Maintain oldestKey (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 const after object literal is unnecessary

keyDetailsFilterFieldConfig.outcomes is already typed by the surrounding declaration; as const here 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

cursorTime is accepted but never emitted in the response, forcing callers to peek at logs[logs.length-1].time.
Explicitly returning nextCursorTime (or hasMore) 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.
getFiveMinuteVerificationTimeseries and friends are more natural and match the existing “TwoHourly”, “ThreeDay” pattern.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c824e5f and bfa27c3.

📒 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 sizing

The 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 sizing

The 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 paths

All 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 correctly

The 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 appearance

The text size in the tooltip has been reduced from text-sm to text-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 handling

Good 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 loading

Converting ApisNavbar to 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 state

The 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 applied

Using dynamic imports for ApisNavbar brings 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 trpc client 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 onNavigate callback 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 children prop to KeysTableActionPopover increases 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 (ControlsContainer and ControlsLeft) and adds date-time filtering capability with LogsDateTime. 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:

  1. Using conditional query execution with enabled: Boolean(requestId)
  2. Disabling automatic refetching behavior for performance
  3. Setting appropriate time range (0 to current timestamp)
  4. 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 onRowMouseEnter and onRowMouseLeave callbacks 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 enablement

This 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 implementation

The 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 aggregation

The 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 provided

The 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 utility

This 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 included

The 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 props

The 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 sizing

The 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> element

The 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 containers

This refactoring improves the code by replacing manually constructed flexbox layouts with more semantic and reusable container components. This change enhances:

  1. Code readability by reducing nesting
  2. Maintainability through consistent component patterns
  3. 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 invalidation

Replacing page refreshes with targeted cache invalidation is a significant improvement. This approach:

  1. Reduces unnecessary re-renders
  2. Improves performance by only refreshing relevant data
  3. Provides a better user experience with less visual disruption

The extracted invalidatePermissions function also improves code reusability.


32-34: Consistent cache invalidation after mutations

Properly 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 experience

Several 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 handling

Good 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 definition

The 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 validations

The 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 experience

Exporting 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 allowMinutelyForVerifications parameter with a default of true maintains 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 onDrawerClose callback, 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:

  1. Centralized options management via getOutcomeOptions()
  2. Clean integration with the filter system through useFilters hook
  3. Proper visualization of filter options with color coding
  4. 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 SummingMergeTree with 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 queries

The 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 middleware

The 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 values

The 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 conversion

The cssVarToChartColor function 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 feedback

The 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 callback

Adding the optional onNavigate callback 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 logic

The 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 management

The component efficiently manages state with useState and useCallback hooks. The callbacks are properly memoized to prevent unnecessary re-renders.


33-36: Good component organization

The 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 inlined

This server component runs only on the server, but dynamic() defaults to client-side chunking.
If ApisNavbar contains 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 MINUTES is generated for minute buckets.
Some ClickHouse versions only accept singular units (MINUTE, HOUR, …).
Please double-check compatibility; if plural forms are not universally supported, keep step singular and rely solely on the numeric stepSize.

Copy link
Collaborator

@mcstepp mcstepp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@ogzhanolguncu ogzhanolguncu added this pull request to the merge queue May 16, 2025
Merged via the queue into main with commit 996c9a9 May 16, 2025
31 of 37 checks passed
@ogzhanolguncu ogzhanolguncu deleted the key-details branch May 16, 2025 16:05
github-merge-queue bot pushed a commit that referenced this pull request May 29, 2025
* 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
@coderabbitai coderabbitai bot mentioned this pull request May 30, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Sep 25, 2025
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants