Skip to content

feat: apis overview v2#2918

Merged
chronark merged 30 commits intomainfrom
apis-overview-v2
Mar 3, 2025
Merged

feat: apis overview v2#2918
chronark merged 30 commits intomainfrom
apis-overview-v2

Conversation

@ogzhanolguncu
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu commented Feb 26, 2025

What does this PR do?

Fixes # (issue)

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

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?

  • Test A
  • Test B

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

  • New Features

    • Introduced a new ApiListCard component to display API information with metrics and visualizations.

    • Added ApiListClient for managing and displaying API lists, including search and empty state handling.

    • Implemented ApiListControlCloud for enhanced filter management in the API list.

    • Added LogsDateTime, LogsRefresh, and LogsSearch components for improved log filtering and searching functionality.

    • Introduced MetricStats and StatsCard components to present statistical data in a structured format.

    • Added new icons, including Key, ChevronUp, and ChevronDown, for enhanced UI clarity.

    • Launched CreateApiButton for a more intuitive API creation process.

  • Refactor

    • Optimized data fetching and pagination for the API overview and logs, ensuring smoother interactions.

    • Restructured API query methods for improved organization and efficiency.

Summary by CodeRabbit

  • New Features
    • Introduced the ApiListCard component for displaying API metrics with visual representations.
    • Launched the ApiListClient component to manage API listings with enhanced user interactions.
    • Added the ApiListControlCloud component for improved filter management.
    • Implemented the LogsDateTime, LogsRefresh, and LogsSearch components for log management and search functionalities.
    • Introduced the MetricStats and StatsCard components for displaying metrics and statistics.
    • Enhanced the search functionality with new components like SearchActions, SearchExampleTooltip, and SearchInput.
    • Added the EmptyComponentSpacer for better handling of empty states in the UI.
    • Introduced the NamespaceCard component for displaying rate limit metrics.
    • Added the ApiListGrid component for displaying API listings in a grid layout.
    • Launched the CreateApiButton component for creating new APIs through a dialog interface.

@vercel
Copy link

vercel bot commented Feb 26, 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 Mar 3, 2025 1:08pm
engineering ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 3, 2025 1:08pm
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 3, 2025 1:08pm
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 3, 2025 1:08pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a broad set of updates across the dashboard. New React components, hooks, and schemas have been added to enhance API listings, filtering, timeseries visualization, and rate-limit monitoring. Legacy components have been removed. Updates to TRPC procedures, ClickHouse queries, and icon sets improve data fetching, granularity handling, and UI consistency. The changes also include adjustments in SQL query construction and audit log query parameters.

Changes

File(s) Change Summary
apps/dashboard/app/(app)/apis/_components/api-list-card.tsx, api-list-client.tsx, api-list-grid.tsx, create-api-button.tsx, client.tsx, page.tsx, actions.ts Added new API listing, creation, and control components; updated API overview fetching logic; removed legacy ApiList component.
apps/dashboard/app/(app)/apis/_components/filters.schema.ts, query-timeseries.schema.ts, hooks/use-filters.ts, hooks/use-query-timeseries.ts, hooks/use-fetch-api-overview.ts Introduced schemas and custom hooks for API filtering and timeseries data fetching (verification & overview).
apps/dashboard/app/(app)/ratelimits/_components/namespace-card.tsx, ratelimits/_components/namespace-card/index.tsx Added a new NamespaceCard component for rate limits and removed a duplicate file.
apps/dashboard/components/logs/chart/utils/format-timestamp.ts, stats-card/components/chart/stats-chart.tsx, stats-card/components/metric-stats.tsx, stats-card/index.tsx Updated timestamp formatting with new granularity options; introduced StatsTimeseriesBarChart, MetricStats, and StatsCard with enhanced type definitions.
apps/dashboard/lib/trpc/routers/api/query-timeseries/*, routers/index.ts, routers/logs/query-timeseries/utils.ts, routers/ratelimit/*, routers/utils/{constants,granularity,granularity.test.ts}, api/overview-api-search.ts, api/query-overview/*, routers/ratelimit/namespace-search.ts Added and refined TRPC procedures for timeseries queries, API overview, and search; updated filtering logic and granularity types; introduced new constants and removed obsolete tests.
internal/clickhouse/src/index.ts, internal/clickhouse/src/verifications.ts Integrated multiple new functions and schemas for verification timeseries queries into the ClickHouse class.
internal/icons/src/icons/{key, chevron-up, chevron-down}.tsx, internal/icons/src/index.ts Added new icon components (Key, ChevronUp, ChevronDown) and updated exports to include them.
apps/dashboard/app/(app)/audit/components/table/hooks/use-logs-query.ts Modified audit logs query to use a shorter historical data window for fetching logs.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant P as ApisOverviewPage (page.tsx)
    participant C as ApiListClient
    participant CT as ApiListControls
    participant G as ApiListGrid
    participant H as useFetchApiOverview
    participant S as TRPC Server

    U->>P: Open API overview page
    P->>S: Call fetchApiOverview()
    S-->>P: Return initial API data
    P->>C: Render API overview with initialData
    C->>H: Initialize API data fetching
    H->>S: Request additional APIs (loadMore)
    S-->>H: Return additional API data
    C->>CT: Render search, refresh, and datetime controls
    C->>G: Render API cards using ApiListCard
Loading
sequenceDiagram
    participant U as User
    participant B as CreateApiButton
    participant F as Form Dialog
    participant T as TRPC Mutation
    participant S as Server
    participant R as Router

    U->>B: Click "Create API" button
    B->>F: Open API creation dialog
    U->>F: Enter API details and submit form
    F->>T: Trigger API creation mutation
    T->>S: Send API creation request
    S-->>T: Return success response with new API data
    T-->>B: Confirm API creation
    B->>R: Redirect to new API dashboard
Loading

Suggested labels

Improvement, Needs Approval, Core Team

Suggested reviewers

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

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 26, 2025

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

@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2025

⚠️ No Changeset found

Latest commit: c3c36ae

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

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: 4

🔭 Outside diff range comments (2)
apps/dashboard/lib/trpc/routers/ratelimit/query-timeseries/utils.ts (1)

24-35: 💡 Verification agent

🧩 Analysis chain

Add unit tests for the refactored function

The function has been updated to use a different granularity type, but there are no apparent tests to ensure that the refactoring maintains the expected behavior.


🏁 Script executed:

#!/bin/bash
# Check for existing tests for this function
echo "Searching for tests for transformRatelimitFilters..."
rg "transformRatelimitFilters" --type test

Length of output: 186


Ensure unit tests cover the new granularity logic in transformRatelimitFilters

It appears that our initial search (using the file type flag) didn’t detect any test files referencing transformRatelimitFilters. Please verify that this function’s new granularity handling is explicitly tested. In particular, add unit tests that assert:

  • The correct transformation of input parameters, especially verifying that the updated granularity value is handled as expected.
  • Proper mapping of any provided identifiers.

Without tests verifying these critical updates, we risk regressions when the function’s behavior changes in future modifications.

apps/dashboard/app/(app)/apis/page.tsx (1)

54-90: 💡 Verification agent

🧩 Analysis chain

Verify if the commented code should be removed.

There's a large block of commented-out code related to unpaid plans. If this functionality is no longer needed, consider removing the commented code entirely rather than leaving it commented out.


🏁 Script executed:

#!/bin/bash
# Check if unpaid plans logic is used elsewhere
rg -A 2 "workspace\.tenantId\.startsWith\(\"org_\"\) && workspace\.plan === \"free\"" --type tsx --type ts

Length of output: 132


Below is the new verification script to re-run the search using file globbing (instead of using the unrecognized tsx file type) so we can confirm whether the unpaid plan logic exists outside the commented code:

Once these commands are executed and you verify that there is no active usage of the unpaid plan logic beyond the commented block, the final recommendation is as follows:

Action: Remove the unused commented unpaid plan logic

  • File: apps/dashboard/app/(app)/apis/page.tsx (Lines 54–90)
    The block starting with the unused unpaid variable and the subsequent commented UI for upgrading plans appears to be legacy code that isn't referenced elsewhere.

If the new script confirms that this logic is not used anywhere else, please remove it entirely from the file.


🏁 Script executed:

#!/bin/bash
# Search for the unpaid plan logic in tsx and ts files using file globbing
echo "Searching for unpaid logic in .tsx files:"
rg -g "*.tsx" 'workspace\.tenantId\.startsWith\("org_"(?:\))?\) && workspace\.plan === "free"'
echo "Searching for unpaid logic in .ts files:"
rg -g "*.ts" 'workspace\.tenantId\.startsWith\("org_"(?:\))?\) && workspace\.plan === "free"'

Length of output: 490


Action Required: Remove the legacy commented code for unpaid plans

The search confirms that the only instance of the "unpaid" logic (using workspace.tenantId.startsWith("org_") && workspace.plan === "free") appears within a commented block in the file. Since there’s no active usage elsewhere in the codebase, please remove this commented section from:

  • File: apps/dashboard/app/(app)/apis/page.tsx (Lines 54–90)

Removing this legacy code will help keep the codebase clean and maintainable.

🧹 Nitpick comments (26)
apps/dashboard/lib/trpc/routers/utils/constants.ts (1)

4-4: Inconsistency in comment for MONTH_IN_MS.

There's a discrepancy in the existing comment for MONTH_IN_MS - it says "30 days in milliseconds" but the calculation uses 31 days.

-export const MONTH_IN_MS = 31 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
+export const MONTH_IN_MS = 31 * 24 * 60 * 60 * 1000; // 31 days in milliseconds
apps/dashboard/app/(app)/apis/_components/controls/components/logs-refresh.tsx (1)

12-15: Consider optimizing the refresh approach.

The component currently calls both api.logs.queryVerificationTimeseries.invalidate() and refresh(). The page refresh will reload the entire page, which might make the query invalidation redundant. Consider whether a full page refresh is necessary, or if invalidating the query and letting TRPC handle the refetch would be more efficient.

 const handleRefresh = () => {
   api.logs.queryVerificationTimeseries.invalidate();
-  refresh();
+  // Only refresh the page if necessary, otherwise TRPC will handle refetching
+  // refresh();
 };
apps/dashboard/app/(app)/apis/_components/controls/index.tsx (1)

7-21: Layout structure could be simplified

The component has unnecessary nesting of div elements that could be simplified while maintaining the same layout.

Consider simplifying the structure:

-    <div className="flex flex-col border-b border-gray-4">
-      <div className="px-3 py-1 w-full justify-between flex items-center">
-        <div className="flex gap-2">
-          <div className="flex gap-2 items-center">
-            <LogsSearch />
-          </div>
-          <div className="flex gap-2 items-center">
-            <LogsDateTime />
-          </div>
-        </div>
-        <div className="flex gap-2">
-          <LogsRefresh />
-        </div>
-      </div>
-    </div>
+    <div className="flex flex-col border-b border-gray-4">
+      <div className="px-3 py-1 w-full justify-between flex items-center">
+        <div className="flex gap-2 items-center">
+          <LogsSearch />
+          <LogsDateTime />
+        </div>
+        <LogsRefresh />
+      </div>
+    </div>
apps/dashboard/app/(app)/apis/_components/hooks/query-timeseries.schema.ts (1)

3-8: Consider adding more validation constraints

The schema looks good, but you could enhance it with additional validations to ensure values are within expected ranges.

Consider adding more specific constraints:

export const verificationQueryTimeseriesPayload = z.object({
-  startTime: z.number().int(),
-  endTime: z.number().int(),
+  startTime: z.number().int().min(0),
+  endTime: z.number().int().min(0),
  since: z.string(),
  keyspaceId: z.string().uuid(),
});

This ensures that time values are non-negative and keyspaceId is a valid UUID format.

apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (1)

5-40: LogsSearch implementation looks good but needs completion

The implementation of the LogsSearch component is well-structured, but there are areas that need attention:

  1. The onSuccess handler is a stub that returns an empty object. Consider implementing the proper success handling.
  2. The commented-out handleClear function references variables (setNamespaces, initialNamespaces) that don't exist in this component.

Either implement the missing functionality or remove the commented-out code to maintain code cleanliness:

- // const handleClear = () => {
-   //   setNamespaces(initialNamespaces);
-   // };

  return (
    <LogsLLMSearch
      hideExplainer
-     // onClear={handleClear}
      placeholder="Search API"
      isLoading={searchNamespace.isLoading}
      onSearch={(query) =>
        searchNamespace.mutateAsync({
          query,
        })
      }
    />
  );
apps/dashboard/components/stats-card/components/metric-stats.tsx (1)

1-28: Well-implemented component with clear structure

The MetricStats component is well-typed and follows good React patterns. The props are properly defined with required and optional values.

Consider using theme variables or design tokens for colors instead of hardcoded Tailwind color classes to improve maintainability:

- <div className="bg-accent-8 rounded h-[10px] w-1" />
+ <div className="bg-success rounded h-[10px] w-1" />

- <div className="bg-orange-9 rounded h-[10px] w-1" />
+ <div className="bg-error rounded h-[10px] w-1" />

This would make it easier to update colors globally when theme changes are needed.

apps/dashboard/components/logs/chart/utils/format-timestamp.ts (1)

32-39: Appropriate formatting for new granularity options

The new cases added for additional granularity options are appropriate and follow the established pattern in the code.

For better maintainability, consider grouping cases with the same format pattern:

  case "perHour":
  case "per2Hours":
  case "per4Hours":
  case "per6Hours":
+ case "per12Hours":
  return format(localDate, "MMM d, HH:mm");

  case "perDay":
+ case "per3Days":
+ case "perWeek":
  return format(localDate, "MMM d");

+ case "perMonth":
+   return format(localDate, "MMM yyyy");
apps/dashboard/lib/trpc/routers/api/query-timeseries/index.ts (1)

7-27: Well-implemented query procedure with proper error handling

The queryVerificationTimeseries procedure is well-structured with good error handling. The code follows the proper patterns for tRPC procedures.

Update the error message to match the verification context:

  throw new TRPCError({
    code: "INTERNAL_SERVER_ERROR",
    message:
-     "Failed to retrieve ratelimit timeseries analytics due to an error. If this issue persists, please contact support@unkey.dev with the time this occurred.",
+     "Failed to retrieve verification timeseries analytics due to an error. If this issue persists, please contact support@unkey.dev with the time this occurred.",
  });

The current error message mentions "ratelimit" instead of "verification" timeseries, which is inconsistent with the procedure's purpose.

apps/dashboard/app/(app)/apis/_components/hooks/use-filters.ts (3)

25-29: Consider stronger typing for filter value

The type casting on line 28 (value: value as string | number) could be improved with more specific validation or explicit type guards to ensure type safety, especially since this value is used in different contexts based on the filter type.

-          value: value as string | number,
+          value: field === "since" ? String(value) : Number(value),

36-59: Add validation for filter values before applying them

The updateFilters function applies values directly without validation. Consider adding validation to ensure that the values match expected formats for each filter type.

 newFilters.forEach((filter) => {
   switch (filter.field) {
     case "startTime":
     case "endTime":
-      newParams[filter.field] = filter.value as number;
+      const numValue = Number(filter.value);
+      if (!isNaN(numValue)) {
+        newParams[filter.field] = numValue;
+      }
       break;
     case "since":
-      newParams.since = filter.value as string;
+      newParams.since = String(filter.value);
       break;
   }
 });

69-73: Consider exporting filter utility types

The hook returns a well-structured object with filters and methods, but you might want to export types for these return values to improve type safety for components consuming this hook.

+export type UseFiltersReturn = {
+  filters: ApiListFilterValue[];
+  removeFilter: (id: string) => void;
+  updateFilters: (newFilters: ApiListFilterValue[]) => void;
+};

 export const useFilters = () => {
   // ... implementation ...

-  return {
+  return {
     filters,
     removeFilter,
     updateFilters,
-  };
+  } as UseFiltersReturn;
 };
apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (3)

18-23: Consider adding pagination for large API lists

The current implementation renders all APIs in a grid, which could lead to performance issues if the list becomes very large. Consider implementing pagination or virtualization for better performance with large datasets.


13-15: Consider adding loading states

The component doesn't appear to handle loading states. If apiList data is fetched asynchronously, adding a loading indicator would improve user experience.

 return (
   <div className="flex flex-col">
     <ApiListControls />
     <ApiListControlCloud />
+    
+    {isLoading ? (
+      <div className="p-5 flex justify-center">
+        <Spinner /> {/* Import this component if needed */}
+      </div>
+    ) : (

11-45: Add error handling for the API list

The component doesn't handle potential error states. Adding error handling would make the component more robust.

-export const ApiListClient = ({ apiList }: { apiList: API[] }) => {
+export const ApiListClient = ({ 
+  apiList, 
+  error 
+}: { 
+  apiList: API[],
+  error?: Error
+}) => {
   return (
     <div className="flex flex-col">
       <ApiListControls />
       <ApiListControlCloud />

       <div className="p-5">
+        {error ? (
+          <Empty>
+            <Empty.Icon />
+            <Empty.Title>Error loading APIs</Empty.Title>
+            <Empty.Description>{error.message}</Empty.Description>
+            <Empty.Actions>
+              <Button onClick={() => window.location.reload()}>Retry</Button>
+            </Empty.Actions>
+          </Empty>
+        ) : 
         {apiList.length > 0 ? (
apps/dashboard/components/stats-card/index.tsx (2)

27-27: Consider making chart height configurable

The chart height is fixed at 120px, which might not be suitable for all use cases. Consider making this configurable through props.

+type StatsCardProps = {
   name: string;
   secondaryId?: string;
   linkPath: string;
   chart: ReactNode;
   stats: ReactNode;
   rightContent?: ReactNode;
   icon?: ReactNode;
+  chartHeight?: string;
 };

 export const StatsCard = ({
   name,
   secondaryId,
   linkPath,
   chart,
   stats,
   rightContent,
   icon = <ProgressBar className="text-accent-11" />,
+  chartHeight = "120px",
 }: StatsCardProps) => {
   return (
     <div className="flex flex-col border border-gray-6 rounded-xl overflow-hidden">
-      <div className="h-[120px]">{chart}</div>
+      <div style={{ height: chartHeight }}>{chart}</div>

6-14: Consider adding a className prop for custom styling

The component doesn't allow for custom styling through a className prop. Adding this would make the component more flexible.

 export type StatsCardProps = {
   name: string;
   secondaryId?: string;
   linkPath: string;
   chart: ReactNode;
   stats: ReactNode;
   rightContent?: ReactNode;
   icon?: ReactNode;
+  className?: string;
 };

 export const StatsCard = ({
   name,
   secondaryId,
   linkPath,
   chart,
   stats,
   rightContent,
   icon = <ProgressBar className="text-accent-11" />,
+  className = "",
 }: StatsCardProps) => {
   return (
-    <div className="flex flex-col border border-gray-6 rounded-xl overflow-hidden">
+    <div className={`flex flex-col border border-gray-6 rounded-xl overflow-hidden ${className}`}>
apps/dashboard/lib/trpc/routers/ratelimit/query-timeseries/utils.ts (1)

5-5: Add documentation for "forRegular" granularity type

The code has been refactored to use a more specific granularity type RegularTimeseriesGranularity and the string literal "forRegular" in function calls, but there's no documentation explaining what this means or how it differs from other potential values.

 import {
   type RegularTimeseriesGranularity,
   type TimeseriesConfig,
   getTimeseriesGranularity,
 } from "../../utils/granularity";

+/**
+ * Transforms filter parameters for rate limit timeseries queries.
+ * Uses the "forRegular" granularity type which is optimized for standard 
+ * timeseries visualization with regular intervals.
+ */
 export function transformRatelimitFilters(params: RatelimitQueryTimeseriesPayload): {
   params: Omit<RatelimitLogsTimeseriesParams, "workspaceId" | "namespaceId">;
   granularity: RegularTimeseriesGranularity;
 } {

Also applies to: 12-12, 14-14, 19-19, 21-21

apps/dashboard/app/(app)/apis/_components/controls/components/logs-datetime/index.tsx (1)

8-88: Well-structured datetime filter component with good accessibility.

The LogsDateTime component effectively integrates with the filtering system and follows accessibility best practices with clear aria attributes. The keyboard shortcut hint in the title attribute is appropriate, as the actual implementation should be in the DatetimePopover component per the established pattern.

A few minor improvement suggestions:

  1. Consider simplifying the filter update logic (lines 33-64) using a more declarative approach with fewer nested conditions.
  2. The default title is set via useEffect, but could be initialized directly in the useState to avoid the extra render cycle:
-const [title, setTitle] = useState<string | null>(null);
+const [title, setTitle] = useState<string>("Last 12 hours");

This would also eliminate the need for the useEffect and null checks.

apps/dashboard/lib/trpc/routers/api/query-timeseries/utils.ts (1)

1-34: Clear and consistent implementation for verification timeseries transformation.

This new utility function follows the same pattern as the regular timeseries transformation, with appropriate specialization for the verification context. The function correctly handles time parameters and returns the expected structure for the API.

One suggestion for consistency and future extensibility:

  return {
    params: {
      tags: [],
      outcomes: [],
+     // Consider adding documentation about what tags and outcomes are
+     // and why they're initialized as empty arrays
      startTime: timeConfig.startTime,
      endTime: timeConfig.endTime,
    },
    granularity: timeConfig.granularity,
  };
apps/dashboard/app/(app)/ratelimits/_components/namespace-card.tsx (1)

16-72: Well-structured rate limit namespace card with good data processing.

The component effectively processes timeseries data to display meaningful metrics and visualizations for rate limiting.

Two optimization suggestions:

  1. Consider memoizing the calculated values to prevent unnecessary recalculations:
-  const passed = timeseries?.reduce((acc, crr) => acc + crr.success, 0) ?? 0;
-  const blocked = timeseries?.reduce((acc, crr) => acc + crr.error, 0) ?? 0;
+  const [passed, blocked] = useMemo(() => {
+    if (!timeseries) return [0, 0];
+    return [
+      timeseries.reduce((acc, crr) => acc + crr.success, 0),
+      timeseries.reduce((acc, crr) => acc + crr.error, 0)
+    ];
+  }, [timeseries]);
  1. For finding the last rate limit entry, you could optimize the approach:
-  const lastRatelimit = timeseries
-    ? timeseries
-        .filter((entry) => entry.total > 0)
-        .sort((a, b) => b.originalTimestamp - a.originalTimestamp)[0]
-    : null;
+  const lastRatelimit = useMemo(() => {
+    if (!timeseries?.length) return null;
+    return timeseries
+      .filter((entry) => entry.total > 0)
+      .reduce((latest, entry) => 
+        !latest || entry.originalTimestamp > latest.originalTimestamp ? entry : latest
+      , null);
+  }, [timeseries]);

This approach uses a single reduce operation rather than filter+sort, which is more efficient for large datasets.

apps/dashboard/lib/trpc/routers/utils/granularity.ts (4)

3-9: Consider consolidating or documenting each interval.

The new VerificationTimeseriesGranularity type enumerates multiple intervals (e.g., perDay, per12Hours, etc.). This is good for clarity, but consider adding short inline documentation or a comment for each interval to outline its intended usage in the verification context. This aids new contributors in quickly understanding the differences.


11-19: Ensure naming consistency among intervals.

The RegularTimeseriesGranularity enumerates multiple intervals including perMinute, per5Minutes, etc. Although everything is functionally correct, the intervals are slightly different from those in VerificationTimeseriesGranularity. If you want to unify them or keep them separate on purpose, consider adding a comment clarifying why the regular intervals differ from the verification intervals.


37-41: Consider adding optional fields (e.g., timeZone, rollupMethod) to TimeseriesConfig.

If you plan to handle time zones or custom aggregations in the future, it may be beneficial to allow for expansions in TimeseriesConfig. Keep the type open for easy extension.


99-119: Regular timeseries intervals appear comprehensive.

These conditionals for the "forRegular" context cover a wide range of intervals down to the minute level. Everything seems correct. Since you have multiple repeated blocks (timeRange >= HOUR_IN_MS * 6 and timeRange >= HOUR_IN_MS * 8 both result in per30Minutes), you might consider a single threshold table or array. This would reduce repetitive code and simplify maintainability.

internal/clickhouse/src/verifications.ts (2)

167-171: Centralized interval definitions.

The TimeInterval type and INTERVALS constant unify all interval logic in one place. This is very beneficial for maintainability. A small suggestion is to document each interval key inside INTERVALS with comments describing typical usage.

Also applies to: 173-230


232-276: Query generation logic is concise.

createVerificationTimeseriesQuery neatly constructs the SELECT statement, grouping, and FILL logic. The dynamic creation of the step in milliseconds is well done. One improvement might be to derive the aggregator map (e.g., 'total', 'valid', 'insufficient_permissions', etc.) directly from the outcome zod enum, ensuring you never forget to add a new aggregator if the enum changes.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 19e2203 and 9054d30.

📒 Files selected for processing (33)
  • apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/control-cloud/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/components/logs-datetime/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/components/logs-refresh.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/filters.schema.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/hooks/query-timeseries.schema.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/hooks/use-filters.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/client.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/page.tsx (4 hunks)
  • apps/dashboard/app/(app)/ratelimits/_components/namespace-card.tsx (1 hunks)
  • apps/dashboard/app/(app)/ratelimits/_components/namespace-card/index.tsx (0 hunks)
  • apps/dashboard/components/logs/chart/utils/format-timestamp.ts (3 hunks)
  • apps/dashboard/components/stats-card/components/chart/stats-chart.tsx (2 hunks)
  • apps/dashboard/components/stats-card/components/metric-stats.tsx (1 hunks)
  • apps/dashboard/components/stats-card/index.tsx (1 hunks)
  • apps/dashboard/lib/trpc/routers/api/query-timeseries/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/api/query-timeseries/utils.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/logs/query-timeseries/utils.test.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/logs/query-timeseries/utils.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/query-latency-timeseries/utils.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/query-timeseries/utils.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/utils/constants.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/utils/granularity.test.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/utils/granularity.ts (1 hunks)
  • internal/clickhouse/src/index.ts (2 hunks)
  • internal/clickhouse/src/verifications.ts (1 hunks)
  • internal/icons/src/icons/key.tsx (1 hunks)
  • internal/icons/src/index.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/dashboard/lib/trpc/routers/logs/query-timeseries/utils.test.ts
  • apps/dashboard/app/(app)/ratelimits/_components/namespace-card/index.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/dashboard/app/(app)/apis/client.tsx
🧰 Additional context used
🧠 Learnings (1)
apps/dashboard/app/(app)/apis/_components/controls/components/logs-datetime/index.tsx (1)
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Test Packages / Test ./packages/rbac
  • GitHub Check: Test Packages / Test ./packages/nextjs
  • GitHub Check: Test Packages / Test ./packages/hono
  • GitHub Check: Test Packages / Test ./packages/cache
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Packages / Test ./internal/id
  • GitHub Check: Test Packages / Test ./internal/billing
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / test_agent_local
  • GitHub Check: Build / Build
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (41)
apps/dashboard/lib/trpc/routers/utils/constants.ts (1)

5-5: LGTM! Added a new time constant for a quarter.

The new QUARTER_IN_MS constant is correctly defined as 3 months by multiplying the existing MONTH_IN_MS constant by 3, following the pattern of other time constants in the file.

internal/icons/src/index.ts (1)

50-50: LGTM! Added export for the new Key icon.

The export for the new Key icon component follows the established pattern in the file.

internal/icons/src/icons/key.tsx (2)

1-39: LGTM! New Key icon component follows established patterns.

The implementation of the Key icon component follows the same structure as other icon components, correctly using the sizeMap for determining pixel size and stroke width based on the provided size prop.


1-11:

Details

❓ Verification inconclusive

Ensure compliance with Nucleo license.

The copyright notice mentions redistribution restrictions from Nucleo. Make sure your usage of this icon complies with the license terms.


🏁 Script executed:

#!/bin/bash
# Check if there's a license file or documentation about Nucleo usage
fd -g "LICENSE*" -g "*license*" -g "*Nucleo*" -i

Length of output: 202


Compliance check on Nucleo license usage required

The file internal/icons/src/icons/key.tsx contains the Nucleo license header, but our automated license search (using the previous fd command) ran into errors and did not return additional documentation. Please manually verify that the usage of these icons fully complies with the Nucleo license terms—especially noting the redistribution restrictions—and confirm that no conflicting or supplemental licensing files (or guidance) exist elsewhere in the repository.

apps/dashboard/app/(app)/apis/_components/controls/components/logs-refresh.tsx (1)

6-18: LGTM! LogsRefresh component correctly handles refresh logic.

The component properly invalidates the verification timeseries query and refreshes the page based on filter conditions.

apps/dashboard/lib/trpc/routers/index.ts (1)

4-4: Proper integration of verification timeseries feature

The addition of a dedicated logs router under the api router and the import of queryVerificationTimeseries creates a well-organized structure for accessing verification timeseries data. This follows the established pattern seen in other parts of the codebase.

Also applies to: 87-89

apps/dashboard/app/(app)/apis/_components/control-cloud/index.tsx (1)

1-30: Component design follows established patterns

The implementation creates a reusable control cloud component specifically for API lists by leveraging existing components and hooks. The formatFieldName function provides good field name formatting for display purposes.

apps/dashboard/components/logs/chart/utils/format-timestamp.ts (1)

1-1: Good update to CompoundTimeseriesGranularity type

The change from TimeseriesGranularity to CompoundTimeseriesGranularity reflects an expanded set of granularity options. This is a logical update that enables the component to handle more diverse time ranges.

Also applies to: 11-11

apps/dashboard/lib/trpc/routers/ratelimit/query-timeseries/utils.ts (1)

16-22:

Details

❓ Verification inconclusive

Consider adding validation for timestamp values

The code uses timestamp values directly from parameters without validation. Consider adding validation to ensure the timestamps are valid before passing them to getTimeseriesGranularity.


🏁 Script executed:

#!/bin/bash
# Check how timestamps are used elsewhere in the codebase
echo "Searching for timestamp validation in the codebase..."
rg -A 2 -B 2 "getTimeseriesGranularity" | rg "valid|check|isValid"

Length of output: 183


Timestamp Validation Check Needed

The revised comment suggests that you add explicit validation for the input timestamp parameters. Our initial search for related validation logic (e.g., usage of keywords like "valid", "check", "isValid") around getTimeseriesGranularity did not return any evidence of built‐in validation elsewhere in the codebase. This raises potential concerns for using params.since, params.startTime, and params.endTime directly—particularly when converting relative timestamps or passing values directly.

  • File: apps/dashboard/lib/trpc/routers/ratelimit/query-timeseries/utils.ts (Lines 16-22)
  • Suggestion: Consider adding safeguards (e.g., explicit checks to ensure each timestamp is a valid numerical value or formatted date) before invoking getTimeseriesGranularity.

Please verify that this change aligns with the expected behavior in other parts of the codebase and that introducing validation does not inadvertently conflict with other timestamp handling logic.

apps/dashboard/lib/trpc/routers/logs/query-timeseries/utils.ts (1)

6-6: Improved type safety with context-specific granularity handling.

The changes properly specialize the granularity type from generic TimeseriesGranularity to more specific RegularTimeseriesGranularity, and update the getTimeseriesGranularity calls to include the appropriate context parameter ("forRegular"). This aligns with similar changes in related files and ensures proper granularity calculation for regular timeseries data.

Also applies to: 13-13, 15-15, 19-19, 21-21

apps/dashboard/lib/trpc/routers/ratelimit/query-latency-timeseries/utils.ts (1)

5-5: Type refinement for timeseries granularity looks good

The changes properly refine the types from generic TimeseriesGranularity to the more specific RegularTimeseriesGranularity and add the "forRegular" context parameter to the getTimeseriesGranularity function. This aligns with the application's broader refactoring of granularity handling.

Also applies to: 12-12, 14-14, 19-21

apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1)

1-62: Well-structured component for displaying API metrics

This component effectively fetches and visualizes verification metrics for an API. The implementation properly handles loading and error states, and calculates aggregated metrics from timeseries data.

apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (1)

52-65: Check for exhaustive error handling in timeseries mapping

The timeseries mapping includes various error states for verification (insufficient_permissions, rate_limited, etc.), but it computes the total error count by subtracting valid from total. Consider if this is the intended behavior or if specific error categories should be summed explicitly.

internal/clickhouse/src/index.ts (1)

46-58: Comprehensive timeseries integration for verifications

The added verification timeseries functions follow the same pattern as other timeseries implementations in the codebase. The timeseries object in the verifications getter provides a complete set of time granularities, maintaining consistency with other parts of the application.

Also applies to: 103-113

apps/dashboard/app/(app)/apis/page.tsx (3)

17-24: Good addition of the API type definition.

Adding proper type definitions improves type safety and makes the component more maintainable. The API type clearly defines the structure expected for API objects.


42-52: Renaming apis to apiList improves clarity.

The variable rename better represents what the data contains. Also, explicitly mapping keyspaceId: api.keyAuthId makes the relationship between these fields clear.


91-91: Good refactoring to use the new ApiListClient component.

Replacing the conditional rendering with a clean direct component improves readability and maintainability.

apps/dashboard/lib/trpc/routers/utils/granularity.test.ts (6)

1-20: Well-structured test setup with fixed date mocking.

The test setup is excellent. Using vi.spyOn(Date, "now") with a fixed timestamp ensures consistent and predictable test results regardless of when the tests are run.


24-46: Good coverage of default parameters for different contexts.

The tests properly verify the default behavior when both startTime and endTime are null, ensuring the function returns appropriate default values based on context.


84-154: Comprehensive test coverage for Regular context granularity selection.

Excellent test coverage with a structured approach using test cases for different time ranges. The inclusion of explicit edge case tests at boundaries (2 hours, 7 days) demonstrates thorough test design.


156-214: Comprehensive test coverage for Verifications context granularity selection.

Similar to the Regular context tests, these tests thoroughly verify the function's behavior for the Verifications context across different time ranges and edge cases.


216-258: Good type compatibility tests.

The tests verify that the function returns properly typed results for both contexts, ensuring type safety throughout the application.


260-305: Practical use case tests enhance confidence in real-world scenarios.

Testing with real-world scenarios like "1-hour dashboard view" and "quarterly verification dashboard" ensures the function behaves correctly in actual usage patterns.

apps/dashboard/app/(app)/apis/_components/filters.schema.ts (3)

8-22: Clear filter configuration with appropriate constraints.

The filter configuration is well-structured with clear type and operator definitions for each field. The configuration limits operators appropriately (only "is" for all fields).


24-31: Good use of Zod for schema validation.

Using Zod for enum definitions provides strong type safety while also enabling runtime validation, which is excellent for handling user input and URL parameters.


32-48: Well-structured types with clear relationships.

The type definitions build upon each other logically, with proper use of generics and utility types. The FilterFieldConfigs type properly constrains the configuration object, and the search params type accurately reflects the expected query parameters.

apps/dashboard/components/stats-card/components/chart/stats-chart.tsx (4)

15-20: Good use of generics with BaseTimeseriesData type.

The introduction of a generic base type with an index signature is an excellent approach. It ensures required properties while allowing flexibility for different data structures.


22-28: Well-designed generic props type for better reusability.

Converting the component props to use generics that extend the base type makes the component much more reusable across different contexts while maintaining type safety.


30-36: Good component rename to reflect broader usage.

Renaming from LogsTimeseriesBarChart to StatsTimeseriesBarChart better represents the component's more generic nature. The addition of the tooltipExtraContent prop enhances customization options.


95-95: Clean implementation of the tooltipExtraContent feature.

The implementation of the optional tooltip extension is elegant and non-intrusive, allowing consumers to augment the tooltip content when needed.

apps/dashboard/lib/trpc/routers/utils/granularity.ts (7)

21-26: Excellent use of context-based type maps.

Defining TimeseriesContext, TimeseriesGranularityMap, and CompoundTimeseriesGranularity keeps the code flexible and type-safe. This separation cleanly distinguishes between verification- and regular-based intervals.

Also applies to: 28-30


32-35: Verify default granularity choices.

DEFAULT_GRANULARITY sets "forVerifications" to "perHour" and "forRegular" to "perMinute". This is perfectly valid. However, if your typical verification queries deal with shorter windows than an hour, or your regular queries commonly deal with multi-hour windows, you might want to revisit these defaults to ensure they reflect typical usage patterns.


44-50: Comprehensive function documentation.

The JSDoc for getTimeseriesGranularity nicely describes parameters and returns. Thank you for clarifying how to use the function. Consider referencing example calls or edge cases in the doc blocks for increased clarity.

Also applies to: 51-55


57-59: Unused constants in code.

WEEK_IN_MS, MONTH_IN_MS, and QUARTER_IN_MS are used properly below, so this is fine. Just ensure to keep them updated if you add more thresholds (e.g., half-year, yearly intervals).


61-61: Sensible fallback behavior.

The fallback path (no startTime / endTime) uses a default duration of one day for verifications and one hour for regular timeseries. This is a good approach to keep queries lightweight. Please confirm with stakeholders that these default windows are aligned with typical usage patterns.

Also applies to: 63-64, 67-68, 70-70, 76-78


82-98: Threshold-based logic for verifications.

The series of conditionals mapping larger time windows to coarser granularities (e.g., perMonth for ≥ 3 months, perWeek for ≥ 2 months, etc.) is clear and straightforward. Just verify you have no off-by-one or inversion of thresholds. For instance, a range of exactly 2 months triggers perWeek—be sure this is intentional.


123-123: Final return structure is intuitive.

Returning the granularity, effective start time, and end time as an object is cleanly structured.

Also applies to: 126-126

internal/clickhouse/src/verifications.ts (4)

150-162: Schema-based approach is solid.

Defining verificationTimeseriesDataPoint with strongly typed fields in the y map is a good pattern for type safety. If you add new outcomes, remember to extend both the outcome enum and this data structure.


164-165: Type exports for reusability.

Exporting VerificationTimeseriesDataPoint and VerificationTimeseriesParams is a helpful move for bridging the UI, TRPC, and DB layers.


278-297: Flexible WHERE clause.

getVerificationTimeseriesWhereClause gracefully handles optional tags and outcomes. Confirm that the logic hasAny(tags, {tags: Array(String)}) is the desired user experience (i.e., an OR match for tags). If an AND match is needed for all tags, you'd have to adapt the query.


299-310: Factory functions streamline usage.

createVerificationTimeseriesQuerier plus the individual exported query functions nicely reduce boilerplate. This pattern is highly modular and keeps the code DRY. Great job!

Also applies to: 312-332

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: 0

🧹 Nitpick comments (5)
apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (5)

14-14: Consider using React Query for data fetching.

The custom hook useFetchVerificationTimeseries appears to be handling loading and error states, but consider using React Query for better caching, retrying, and stale data management.


16-17: Consider memoizing calculated values.

These reduce operations run on every render. Use useMemo to prevent recalculation when the timeseries hasn't changed:

- const passed = timeseries?.reduce((acc, crr) => acc + crr.success, 0) ?? 0;
- const blocked = timeseries?.reduce((acc, crr) => acc + crr.error, 0) ?? 0;
+ const [passed, blocked] = useMemo(() => {
+   const passed = timeseries?.reduce((acc, crr) => acc + crr.success, 0) ?? 0;
+   const blocked = timeseries?.reduce((acc, crr) => acc + crr.error, 0) ?? 0;
+   return [passed, blocked];
+ }, [timeseries]);

29-38: Add number formatting for improved readability.

Consider formatting large numbers with thousands separators for better readability in the chart. You could use the built-in Intl.NumberFormat or a lightweight library.


49-54: Enhance accessibility for key count display.

Add an aria-label to improve screen reader experience for the key count section:

- <div className="flex items-center gap-2 min-w-0 max-w-[40%]">
+ <div className="flex items-center gap-2 min-w-0 max-w-[40%]" aria-label={`${api.keys.length || 0} API keys`}>

43-48: Improve number formatting for metric stats.

For better readability, consider formatting the large numbers with thousands separators:

<MetricStats
- successCount={passed}
- errorCount={blocked}
+ successCount={new Intl.NumberFormat().format(passed)}
+ errorCount={new Intl.NumberFormat().format(blocked)}
  successLabel="VALID"
  errorLabel="INVALID"
/>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9054d30 and 6c3dd03.

📒 Files selected for processing (2)
  • apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/app/(app)/apis/_components/api-list-client.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Build / Build
🔇 Additional comments (1)
apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1)

1-60: Overall component looks well structured.

The ApiListCard component is well-designed with proper error handling, loading states, and a clean layout using the StatsCard component. The component correctly calculates metrics from timeseries data and displays them appropriately.

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: 3

♻️ Duplicate comments (2)
internal/clickhouse/src/verifications.ts (2)

142-142: ⚠️ Potential issue

Parameter naming mismatch

verificationTimeseriesParams uses keyspaceId whereas older code uses keySpaceId. Ensure naming consistency across the codebase.

-  keyspaceId: z.string(),
+  keySpaceId: z.string(),

267-267: ⚠️ Potential issue

Fix parameter name mismatch in SQL query

There's a mismatch between the parameter name in schema (keyspaceId) and the one used in SQL (keyspaceId).

-    "key_space_id = {keyspaceId: String}",
+    "key_space_id = {keySpaceId: String}",
🧹 Nitpick comments (15)
apps/dashboard/lib/trpc/routers/api/overview-api-search.ts (1)

18-19: Consider adding error handling for database operations.

The procedure lacks error handling for the database operation. If the query fails, the error will propagate without a specific error message.

- const apiList = apiItemsWithKeyCounts(apis);
- return apiList;
+ try {
+   const apiList = apiItemsWithKeyCounts(apis);
+   return apiList;
+ } catch (error) {
+   console.error("Error processing API list with key counts:", error);
+   throw new TRPCError({
+     code: "INTERNAL_SERVER_ERROR",
+     message: "Failed to process API data",
+   });
+ }
apps/dashboard/lib/trpc/routers/api/query-overview/index.ts (1)

19-19: Improve error logging structure for better observability.

The current error logging uses string concatenation with JSON.stringify, which may not capture all error details properly.

- console.error("Something went wrong when fetching api overview list", JSON.stringify(error));
+ console.error("Something went wrong when fetching api overview list", { 
+   error: error instanceof Error ? { message: error.message, stack: error.stack } : error 
+ });
apps/dashboard/app/(app)/apis/_components/controls/index.tsx (2)

12-30: Well-structured control component with potential for simplification.

The component properly organizes controls for API list management, but the nested div structure could be simplified.

Consider flattening the nested div structure to improve maintainability:

export function ApiListControls(props: Props) {
  return (
    <div className="flex flex-col border-b border-gray-4">
-      <div className="px-3 py-1 w-full justify-between flex items-center">
-        <div className="flex gap-2">
-          <div className="flex gap-2 items-center">
-            <LogsSearch {...props} />
-          </div>
-          <div className="flex gap-2 items-center">
-            <LogsDateTime />
-          </div>
-        </div>
-        <div className="flex gap-2">
-          <LogsRefresh />
-        </div>
-      </div>
+      <div className="px-3 py-1 w-full flex items-center justify-between">
+        <div className="flex gap-2 items-center">
+          <LogsSearch {...props} />
+          <LogsDateTime />
+        </div>
+        <LogsRefresh />
+      </div>
    </div>
  );
}

18-18: Consider explicit prop forwarding for better maintainability.

Spreading all props to the LogsSearch component might lead to unexpected behavior if props change in the future.

- <LogsSearch {...props} />
+ <LogsSearch 
+   apiList={props.apiList} 
+   onApiListChange={props.onApiListChange} 
+   onSearch={props.onSearch} 
+ />
apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (1)

1-62: Well structured search component with good state management.

The implementation is clean and handles the search state appropriately using refs to preserve the original list. The error handling with toast notifications is comprehensive with good UX considerations.

A potential enhancement would be to add a debounce mechanism for the search functionality to prevent excessive API calls when users type quickly.

apps/dashboard/lib/trpc/routers/api/query-overview/schemas.ts (1)

28-31: Well-constrained query parameters.

The limit constraints (min 1, max 18, default 9) are clearly defined. Consider adding documentation comments to explain the reasoning behind these specific values for future maintainers.

 export const queryApisOverviewPayload = z.object({
+  // Limit of 18 represents two full rows in a 3x3 grid layout on larger screens
   limit: z.number().min(1).max(18).default(9),
   cursor: Cursor.optional(),
 });
apps/dashboard/app/(app)/apis/_components/hooks/use-fetch-api-overview.ts (1)

10-53: Solid hook implementation with minor concerns.

The hook implementation is well-structured with proper state management and pagination handling. However, there's a potential issue with the dependencies in the useEffect.

Including total in the dependency array (line 40) while conditionally updating it based on comparison with the same value could potentially cause unnecessary re-renders.

Consider removing total from the dependency array or using a ref to track the previous value:

-  }, [total, data, setApiList]);
+  }, [data, setApiList]);
apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (1)

26-30: Consider adding a fallback for empty API list.

The component doesn't handle the case when apiList is empty. Users might see an empty grid without any explanation.

Consider adding a fallback message when no APIs are available:

 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full max-w-7xl">
+  {apiList.length === 0 ? (
+    <div className="col-span-full text-center py-10 text-accent-11">
+      No APIs found. {isSearching ? "Try adjusting your search." : ""}
+    </div>
+  ) : (
     {apiList.map((api) => (
       <ApiListCard api={api} key={api.id} />
     ))}
+  )}
 </div>
apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1)

16-17: Simplified null handling for reduce operations.

The nullish coalescing operator is redundant since reduce with an initial value of 0 will return 0 for empty or undefined arrays.

Consider simplifying:

-  const passed = timeseries?.reduce((acc, crr) => acc + crr.success, 0) ?? 0;
-  const blocked = timeseries?.reduce((acc, crr) => acc + crr.error, 0) ?? 0;
+  const passed = timeseries?.reduce((acc, crr) => acc + crr.success, 0) || 0;
+  const blocked = timeseries?.reduce((acc, crr) => acc + crr.error, 0) || 0;

Or even cleaner:

  const passed = timeseries ? timeseries.reduce((acc, crr) => acc + crr.success, 0) : 0;
  const blocked = timeseries ? timeseries.reduce((acc, crr) => acc + crr.error, 0) : 0;
apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (2)

20-40: Consider moving filter type validation to schema level.

The current implementation manually checks filter types and logs errors to the console, which isn't visible to users. This validation should ideally happen at the schema level.

Consider using a validation library like Zod or improving the error handling to provide user feedback for invalid filters.

Also, the switch statement could be simplified with a type guard function:

function isNumberFilter(filter: Filter): filter is Filter & { value: number } {
  return typeof filter.value === 'number';
}

// Then in the switch:
case 'startTime':
case 'endTime': {
  if (!isNumberFilter(filter)) {
    console.error(`${filter.field} filter value type has to be 'number'`);
    return;
  }
  params[filter.field] = filter.value;
  break;
}

52-59: Add type safety for timeseries transform.

The data transformation assumes certain shapes and properties in the response, which might lead to runtime errors if the API response changes.

Consider adding type guards or fallbacks to handle potential undefined values:

const timeseries = data?.timeseries.map((ts) => ({
  displayX: formatTimestampForChart(ts.x, data.granularity),
  originalTimestamp: ts.x,
  valid: ts.y?.valid ?? 0,
  total: ts.y?.total ?? 0,
  success: ts.y?.valid ?? 0,
  error: (ts.y?.total ?? 0) - (ts.y?.valid ?? 0),
}));
apps/dashboard/app/(app)/apis/actions.ts (2)

51-51: Consider using a more specific type than Array<any>

Using any type reduces type safety and can lead to potential runtime errors. Consider defining a specific interface or type for the API items.

-export async function apiItemsWithKeyCounts(apiItems: Array<any>) {
+export async function apiItemsWithKeyCounts(apiItems: Array<{id: string, name: string, keyAuthId: string | null}>) {

51-67: Consider batching database queries for better performance

The current implementation makes a separate database query for each API item. For a large number of APIs, this could lead to performance issues.

Consider using a batched approach with a single query that uses IN condition on keyAuthId:

 export async function apiItemsWithKeyCounts(apiItems: Array<any>) {
-  return await Promise.all(
-    apiItems.map(async (api) => {
-      const keyCountResult = await db
-        .select({ count: sql<number>`count(*)` })
-        .from(schema.keys)
-        .where(and(eq(schema.keys.keyAuthId, api.keyAuthId!), isNull(schema.keys.deletedAtM)));
-      const keyCount = Number(keyCountResult[0]?.count || 0);
-      return {
-        id: api.id,
-        name: api.name,
-        keyspaceId: api.keyAuthId,
-        keys: [{ count: keyCount }],
-      };
-    }),
-  );
+  // Extract all keyAuthIds (filtering out nulls)
+  const keyAuthIds = apiItems
+    .map(api => api.keyAuthId)
+    .filter(Boolean);
+  
+  // If no valid keyAuthIds, return transformed items with zero counts
+  if (keyAuthIds.length === 0) {
+    return apiItems.map(api => ({
+      id: api.id,
+      name: api.name,
+      keyspaceId: api.keyAuthId,
+      keys: [{ count: 0 }],
+    }));
+  }
+  
+  // Single query to get counts for all keyAuthIds
+  const keyCountsResult = await db
+    .select({
+      keyAuthId: schema.keys.keyAuthId,
+      count: sql<number>`count(*)`,
+    })
+    .from(schema.keys)
+    .where(and(
+      schema.keys.keyAuthId.in(keyAuthIds),
+      isNull(schema.keys.deletedAtM)
+    ))
+    .groupBy(schema.keys.keyAuthId);
+  
+  // Convert to a map for O(1) lookup
+  const keyCountMap = new Map(
+    keyCountsResult.map(result => [result.keyAuthId, result.count])
+  );
+  
+  // Map original items with their counts
+  return apiItems.map(api => ({
+    id: api.id,
+    name: api.name,
+    keyspaceId: api.keyAuthId,
+    keys: [{ count: Number(keyCountMap.get(api.keyAuthId) || 0) }],
+  }));
apps/dashboard/app/(app)/apis/page.tsx (1)

32-35: Add error handling for fetchApiOverview

The function call to fetchApiOverview lacks error handling, which could lead to unhandled exceptions if the fetch fails.

-  const initialData = await fetchApiOverview({
-    workspaceId: workspace.id,
-    limit: DEFAULT_OVERVIEW_FETCH_LIMIT,
-  });
+  const initialData = await fetchApiOverview({
+    workspaceId: workspace.id,
+    limit: DEFAULT_OVERVIEW_FETCH_LIMIT,
+  }).catch(error => {
+    console.error("Failed to fetch API overview:", error);
+    return { apiList: [], hasMore: false, nextCursor: undefined, total: 0 };
+  });
internal/clickhouse/src/verifications.ts (1)

224-262: Consider adding JSDoc comments for complex functions

The createVerificationTimeseriesQuery function is complex and would benefit from JSDoc comments explaining its purpose, parameters, and return value.

+/**
+ * Creates a SQL query for fetching verification timeseries data
+ * 
+ * @param interval - The time interval configuration with table, step and stepSize
+ * @param whereClause - The WHERE clause for filtering the data
+ * @returns A SQL query string for fetching verification timeseries data
+ */
 function createVerificationTimeseriesQuery(interval: TimeInterval, whereClause: string) {
   // Function implementation...
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c3dd03 and a40c2d1.

📒 Files selected for processing (21)
  • apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/create-api-button.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/hooks/use-fetch-api-overview.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/actions.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/client.tsx (0 hunks)
  • apps/dashboard/app/(app)/apis/page.tsx (3 hunks)
  • apps/dashboard/lib/trpc/routers/api/overview-api-search.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/api/query-overview/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/api/query-overview/schemas.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (2 hunks)
  • apps/dashboard/package.json (1 hunks)
  • internal/clickhouse/src/verifications.ts (1 hunks)
  • internal/icons/src/icons/chevron-down.tsx (1 hunks)
  • internal/icons/src/icons/chevron-up.tsx (1 hunks)
  • internal/icons/src/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/dashboard/app/(app)/apis/client.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/dashboard/app/(app)/apis/_components/constants.ts
  • apps/dashboard/app/(app)/apis/_components/create-api-button.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/icons/src/index.ts
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Test Packages / Test ./packages/rbac
  • GitHub Check: Test Go API Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: autofix
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (26)
internal/icons/src/icons/chevron-up.tsx (5)

1-11: Copyright notice is properly included.

The file appropriately includes the required Nucleo copyright notice and license information, which is important for proper attribution and compliance with the icon library's licensing terms.


12-14: Import statements are correct and concise.

The component correctly imports React types and the required IconProps and sizeMap from the props file. Using type imports for React and explicit type imports helps with tree-shaking and bundle optimization.


15-17: Component declaration and default props are well-implemented.

The ChevronUp component is correctly defined as a functional component with proper TypeScript typing. Default size of "xl-thin" is appropriately set, and props spreading is correctly implemented.


18-33: SVG implementation follows best practices.

The SVG component is properly implemented with:

  • Spread props for extensibility
  • Consistent height/width based on the pixelSize from sizeMap
  • Proper viewBox attribute
  • Use of "currentColor" for theming support

This implementation allows the icon to inherit colors from its parent and be sized appropriately based on the predefined sizes.


34-35: Component export is properly defined.

The component is correctly exported as a named export, making it available for import in other files.

internal/icons/src/icons/chevron-down.tsx (5)

1-11: Copyright notice is properly included.

The file appropriately includes the required Nucleo copyright notice and license information, which is important for proper attribution and compliance with the icon library's licensing terms.


12-14: Import statements are correct and concise.

The component correctly imports React types and the required IconProps and sizeMap from the props file. Using type imports for React and explicit type imports helps with tree-shaking and bundle optimization.


15-17: Component declaration and default props are well-implemented.

The ChevronDown component is correctly defined as a functional component with proper TypeScript typing. Default size of "xl-thin" is appropriately set, and props spreading is correctly implemented.


18-33: SVG implementation follows best practices.

The SVG component is properly implemented with:

  • Spread props for extensibility
  • Consistent height/width based on the pixelSize from sizeMap
  • Proper viewBox attribute
  • Use of "currentColor" for theming support

This implementation allows the icon to inherit colors from its parent and be sized appropriately based on the predefined sizes.


34-35: Component export is properly defined.

The component is correctly exported as a named export, making it available for import in other files.

apps/dashboard/package.json (1)

95-95: Appropriate addition of react-intersection-observer library.

The react-intersection-observer package will allow components to detect when elements enter or leave the viewport, which is useful for implementing features like lazy loading or triggering animations based on visibility.

apps/dashboard/lib/trpc/routers/api/query-overview/index.ts (1)

6-25: Well-implemented rate-limited query procedure with proper error handling.

The procedure correctly validates input/output using schemas and properly handles errors with appropriate messaging.

apps/dashboard/lib/trpc/routers/index.ts (2)

4-6: LGTM! Clean import additions.

The imports are well organized and follow the project's existing pattern.


89-95: New router structure follows existing patterns.

The addition of the nested routers for logs and overview follows the established pattern in the codebase, providing a clean organization of related endpoints.

apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (3)

20-21: LGTM! State management is appropriate.

The component correctly initializes state from props.


26-35: Potential conditional rendering inconsistency.

The component checks initialData.apiList.length for conditional rendering, but then uses the apiList state variable for displaying the grid. If apiList state is updated to an empty array while initialData.apiList remains non-empty, this could lead to unexpected rendering behavior.

Consider using the same reference for the conditional check:

-      {initialData.apiList.length > 0 ? (
+      {apiList.length > 0 ? (

36-59: Well-designed empty state with clear actions.

The empty state provides good user guidance with both a create button and documentation link.

apps/dashboard/lib/trpc/routers/api/query-overview/schemas.ts (2)

3-13: LGTM! Well-defined schema for API overview.

The schema is properly structured using Zod's object and array methods.


19-26: LGTM! Response schema with pagination support.

The response schema properly includes pagination metadata (hasMore, nextCursor) along with the result list and total count.

apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (1)

11-55: Well-structured component with good UX for loading states.

The component is well-implemented with proper handling of loading states and pagination. The conditional rendering for the "Load more" button based on isSearching and hasMore is a good practice.

apps/dashboard/app/(app)/apis/_components/api-list-card.tsx (1)

13-61: Nice implementation of the API card with data visualization.

The component effectively handles loading and error states, calculates summary statistics, and displays them in a visually appealing way.

apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (1)

15-15: Verify Time Window Calculation Logic

After checking the definition and usage of TIMESERIES_DATA_WINDOW, it's clear that the constant is defined as a one-hour interval (i.e., 60 * 60 * 1000 milliseconds). However, in this hook the calculation is done as:

-      startTime: dateNow - TIMESERIES_DATA_WINDOW * 24,
+      startTime: dateNow - TIMESERIES_DATA_WINDOW,

This discrepancy changes the time window from one hour to 24 hours.

apps/dashboard/app/(app)/apis/actions.ts (1)

57-57: Add null check before using non-null assertion

The keyAuthId property is being accessed with a non-null assertion (!), but it's not clear if this property is always guaranteed to exist or be non-null.

-        .where(and(eq(schema.keys.keyAuthId, api.keyAuthId!), isNull(schema.keys.deletedAtM)));
+        .where(and(
+          eq(schema.keys.keyAuthId, api.keyAuthId ?? ''),  // Handle null/undefined case
+          isNull(schema.keys.deletedAtM)
+        ));
apps/dashboard/app/(app)/apis/page.tsx (2)

47-47: Add null/undefined check for initialData

The code assumes initialData.apiList always exists, which might not be true if there was an error fetching the data.

-            defaultOpen={initialData.apiList.length === 0 || props.searchParams.new}
+            defaultOpen={initialData?.apiList?.length === 0 || props.searchParams.new}

73-73: Add null/undefined check when passing initialData

Ensure initialData is valid before passing it to the ApiListClient component.

-        <ApiListClient initialData={initialData} />
+        <ApiListClient initialData={initialData ?? { apiList: [], hasMore: false, nextCursor: undefined, total: 0 }} />
internal/clickhouse/src/verifications.ts (1)

245-245: Add fallback for msPerUnit lookup

The non-null assertion on msPerUnit! assumes the lookup will always succeed, but it's better to provide a fallback.

-  const stepMs = msPerUnit! * interval.stepSize;
+  const stepMs = (msPerUnit ?? 3600_000) * interval.stepSize;  // Default to hour if not found

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: 3

🧹 Nitpick comments (9)
apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (2)

21-41: Enhance error handling beyond console logs

While you correctly validate filter types and log errors, consider providing user feedback or fallback behavior when invalid filter values are encountered.

 switch (filter.field) {
   case "startTime":
   case "endTime": {
     if (typeof filter.value !== "number") {
       console.error(`${filter.field} filter value type has to be 'number'`);
+      // Consider dispatching to a toast notification system or error state
+      // that can be displayed to the user
       return;
     }
     params[filter.field] = filter.value;
     break;
   }
   case "since": {
     if (typeof filter.value !== "string") {
       console.error("Since filter value type has to be 'string'");
+      // Consider dispatching to a toast notification system or error state
+      // that can be displayed to the user
       return;
     }
     params.since = filter.value;
     break;
   }
 }

61-68: Add fallback for undefined data

The current implementation uses optional chaining, but consider providing an explicit empty array fallback when data is undefined.

-const timeseries = data?.timeseries.map((ts) => ({
+const timeseries = data?.timeseries?.map((ts) => ({
   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,
-}));
+})) || [];
apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (2)

12-36: Consider using deep copy for originalApiList to prevent reference issues

The component logic is well-structured, but be aware that using spread operator on line 19 only creates a shallow copy of the apiList array. If ApiOverview objects contain nested properties that might be mutated, consider using a deep copy method instead.

- originalApiList.current = [...apiList];
+ originalApiList.current = JSON.parse(JSON.stringify(apiList));

47-62: Consider adding accessibility attributes to the search component

The search implementation is solid, but adding aria-label or aria-labelledby would enhance accessibility for screen reader users.

  <LogsLLMSearch
    hideExplainer
    onClear={handleClear}
    placeholder="Search API using name or ID"
    isLoading={searchApiOverview.isLoading}
    loadingText="Searching APIs..."
    searchMode="allowTypeDuringSearch"
+   aria-label="Search APIs"
    onSearch={(query) =>
      searchApiOverview.mutateAsync({
        query,
      })
    }
  />
apps/dashboard/components/logs/llm-search/components/search-example-tooltip.tsx (1)

8-52: Well-structured tooltip with clear example presentation

The SearchExampleTooltip component is well-implemented with good use of the Tooltip components. The examples are clearly presented with proper styling and interaction handling.

Two small suggestions for enhancement:

  1. Consider adding keyboard navigation support for the example buttons
  2. The delay duration of 150ms might be too short for some users - consider increasing it slightly
- <Tooltip delayDuration={150}>
+ <Tooltip delayDuration={300}>
apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.ts (3)

57-57: Missing explanation for lint rule suppression.

The biome-ignore comment lacks an explanation for why the exhaustive dependencies rule is being suppressed. Adding an explanation would improve code maintainability.

-// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
+// biome-ignore lint/correctness/useExhaustiveDependencies: throttledSearch has a recursive call that requires the function itself in dependencies

32-34: Consider using a more sophisticated error handling approach.

Using console.error in production code might not be appropriate. Consider implementing a more robust error handling strategy or injecting a logger.

-        } catch (error) {
-          console.error("Search failed:", error);
-        }
+        } catch (error) {
+          // Use a configurable logger or error reporting service
+          console.error("Search failed:", error);
+          // Optionally: notify the user about the error
+        }

64-68: Simplify conditional logic for better readability.

The current condition combines two checks which could be separated or rewritten for better clarity.

-      // If this is the first search, use debounced search
-      if (lastSearchTimeRef.current === 0 && query) {
+      // If this is the first search and query is not empty, use debounced search
+      const isFirstSearch = lastSearchTimeRef.current === 0;
+      if (isFirstSearch && query) {
apps/dashboard/components/logs/llm-search/components/search-actions.tsx (1)

27-29: Simplify the condition using positive logic.

The double negative in this condition makes it harder to read and understand. Consider rewriting it with positive logic.

-  // Don't render anything if processing (unless in allowTypeDuringSearch mode)
-  if (!(!isProcessing || searchMode === "allowTypeDuringSearch")) {
+  // Don't render anything if processing (unless in allowTypeDuringSearch mode)
+  if (isProcessing && searchMode !== "allowTypeDuringSearch") {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 746e742 and 88fbb72.

📒 Files selected for processing (20)
  • apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/audit/components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/logs/components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/ratelimits/_components/controls/components/logs-search/index.tsx (2 hunks)
  • apps/dashboard/app/(app)/ratelimits/_components/ratelimit-client.tsx (2 hunks)
  • apps/dashboard/components/empty-component-spacer.tsx (1 hunks)
  • apps/dashboard/components/logs/llm-search/components/search-actions.tsx (1 hunks)
  • apps/dashboard/components/logs/llm-search/components/search-example-tooltip.tsx (1 hunks)
  • apps/dashboard/components/logs/llm-search/components/search-icon.tsx (1 hunks)
  • apps/dashboard/components/logs/llm-search/components/search-input.tsx (1 hunks)
  • apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.test.tsx (1 hunks)
  • apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.ts (1 hunks)
  • apps/dashboard/components/logs/llm-search/index.tsx (3 hunks)
  • apps/dashboard/components/stats-card/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/controls/components/logs-search/index.tsx
  • apps/dashboard/app/(app)/logs/components/controls/components/logs-search/index.tsx
  • apps/dashboard/app/(app)/audit/components/controls/components/logs-search/index.tsx
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/controls/components/logs-search/index.tsx
  • apps/dashboard/components/stats-card/index.tsx
  • apps/dashboard/app/(app)/ratelimits/_components/controls/components/logs-search/index.tsx
🧰 Additional context used
🧠 Learnings (1)
apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (1)
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2918
File: apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts:0-0
Timestamp: 2025-02-27T14:08:53.557Z
Learning: The useFetchVerificationTimeseries hook intentionally uses TIMESERIES_DATA_WINDOW * 24 to create a daily interval (24 hours) instead of the standard 1-hour interval used in other hooks.
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Go API Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Build / Build
🔇 Additional comments (31)
apps/dashboard/components/empty-component-spacer.tsx (1)

1-9: Well-implemented reusable component for empty states.

This is a clean implementation of a reusable component that provides consistent spacing and centering for empty states throughout the application. Using TypeScript's PropsWithChildren type is appropriate here.

Some minor observations:

  • The fixed minimum height of 300px ensures consistent empty state sizing
  • The nested div structure achieves proper centering of content
  • The component follows the single responsibility principle
apps/dashboard/app/(app)/apis/page.tsx (3)

2-6: Good refactoring of imports to support the new API overview implementation.

The imports have been updated appropriately to support the new implementation, removing deprecated dependencies and adding references to the new components and constants.


28-31: Good implementation of data fetching with pagination.

The new fetchApiOverview function with limit parameter supports better pagination and performance by limiting the initial data load. Using a constant for the fetch limit is a good practice for maintainability.


36-37: Clean update to page rendering with proper prop passing.

The component now correctly passes initialData.total to the Navigation component and delegates rendering to the new ApiListClient component with appropriate props.

apps/dashboard/app/(app)/apis/_components/api-list-client.tsx (6)

3-15: Good organization of imports and type definitions.

The imports are well-structured, separating local components from UI libraries and types. Including specific types from the schema files instead of importing the entire schema is a good practice for tree-shaking.


16-25: Well-defined component props with proper state management.

The component properly defines its props with TypeScript types and initializes state using the provided initial data. The separation of search state from the API list state is good for component logic organization.


26-45: Good handling of unpaid workspace state.

The component appropriately handles the unpaid workspace case by showing a clear upgrade prompt with actionable next steps. Using the EmptyComponentSpacer provides consistent spacing and styling across empty states.


47-51: Clean layout implementation with proper component composition.

The component's main layout effectively combines controls and cloud components before conditionally rendering either the grid or empty state.


51-58: Well-implemented conditional rendering of API list.

The grid is only rendered when API data exists, and all necessary props are passed down to the child component.


59-81: Good implementation of empty state with actionable options.

The empty state provides clear messaging and actionable next steps for users who haven't created any APIs yet. Including both a create button and documentation link gives users multiple paths forward.

apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (5)

1-11: Appropriate imports with specific type definitions.

The component correctly imports the necessary types and components, including the specialized hook for API data fetching.


12-23: Well-typed component props with appropriate optionality.

The component defines its props with proper TypeScript types, making isSearching optional which is good for flexibility. The use of React's Dispatch<SetStateAction<>> type for the setter function is correct.


25-37: Good empty state handling for search results.

The component properly handles the case when no APIs match the search criteria, showing a clear empty state with appropriate messaging.


41-45: Responsive grid implementation with good spacing.

The grid layout uses responsive column counts based on viewport size, which provides a good user experience across different devices. The use of gap spacing creates proper visual separation between cards.


46-65: Well-implemented pagination with loading state.

The load more functionality includes:

  • Clear indication of how many items are being shown
  • Conditional rendering of the load more button based on search state and available data
  • Good loading state feedback with animation
  • Proper button disabling during loading
apps/dashboard/app/(app)/ratelimits/_components/ratelimit-client.tsx (3)

3-3: Good addition of the EmptyComponentSpacer component.

Using a dedicated component for empty state spacing improves consistency across the dashboard.


39-45: Well-implemented responsive grid layout.

The responsive grid implementation is excellent, with appropriate column scaling from mobile to large desktop screens. Using CSS Grid with breakpoints ensures a consistent and adaptive layout across different device sizes.


46-74: Empty state handling is well-designed and user-friendly.

The empty state implementation provides clear guidance with:

  • Informative message explaining the situation
  • Practical code example with copy functionality
  • Direct link to relevant documentation

This follows best practices for empty states by not just showing a message but providing actionable next steps for users.

apps/dashboard/app/(app)/apis/_components/hooks/use-query-timeseries.ts (3)

1-6: Good job on the imports organization

The imports are well organized, including necessary utilities for formatting timestamps, constants, trpc client, and React hooks.


16-16: Confirm daily interval time window calculation

The multiplication by 24 creates a daily interval (24 hours) instead of the standard 1-hour interval used in other hooks, which is intentional as per previous discussions.


8-76: Well-structured custom hook implementation

Overall, this is a well-designed custom hook that:

  1. Properly uses useMemo to avoid recreating objects on each render
  2. Processes filters correctly with appropriate type checking
  3. Implements throttling to prevent excessive database load
  4. Transforms data into a chart-friendly format
  5. Handles conditional refetching based on query parameters

Good job on the implementation!

apps/dashboard/components/logs/llm-search/components/search-icon.tsx (1)

1-13: Well-structured and clear component implementation!

This is a clean implementation of a conditional icon component that provides visual feedback during search operations. The use of data-testid attributes will be helpful for component testing.

apps/dashboard/app/(app)/apis/_components/controls/components/logs-search/index.tsx (1)

38-45: LGTM! Clear state reset handling

The handleClear function cleanly resets the state when the search is cleared, maintaining a good user experience.

apps/dashboard/components/logs/llm-search/components/search-input.tsx (3)

1-12: Props interface is well-defined

The type definition clearly outlines all required properties with appropriate types. Including the searchMode with strict string literal options is a good practice.


27-33: Clean conditional rendering of loading state

The conditional rendering of loading state is well-implemented, with a clear data-testid for testing purposes.


35-49:

Details

❓ Verification inconclusive

Reconsider autoFocus for better accessibility

While the code comment explains the reasoning for autoFocus, this can cause accessibility issues for keyboard users who may be unexpectedly moved to this input. Consider implementing a more accessible focus management approach.


🌐 Web query:

What are the accessibility concerns with using autoFocus in React?

💡 Result:

Using the autoFocus attribute in React can enhance user experience by directing attention to key elements, but it raises several accessibility concerns that developers must address:

Disrupted Navigation for Assistive Technologies

  • Screen readers may skip over critical content preceding the autofocused element[1][8]. For example, form instructions might be missed if focus jumps immediately to an input field[1].
  • Keyboard users expect predictable focus order (Tab key navigation). Autofocus can disrupt this flow, violating WCAG 2.4.3 (Focus Order)[1][8].

Unexpected Context Shifts

  • On mobile devices, autofocus often triggers abrupt scrolling to the targeted element[1][10].
  • Users with cognitive disabilities may become disoriented if focus changes without explicit interaction[1][4].

WCAG Compliance Risks

Autofocus misuse can conflict with:

  1. Success Criterion 1.3.2 (Meaningful Sequence): Content order must remain logical[1].
  2. Success Criterion 3.2.1 (On Focus): Focus changes shouldn’t alter context (e.g., submitting forms prematurely)[1].

Best Practices for Accessible Autofocus

  1. Use sparingly: Only apply when absolutely necessary (e.g., search bars in single-input modals)[5][8].
  2. Test extensively: Verify behavior with screen readers (NVDA/JAWS) and keyboard-only navigation[1][10].
  3. Prioritize manual focus control: Use React’s useRef and useEffect for programmatic focus management instead of autoFocus[3][6]:
function ControlledInput() {
  const inputRef = useRef(null);
  useEffect(() => { inputRef.current.focus(); }, []);
  return <input ref={inputRef} />;
}
  1. Consider alternatives: For modals, use libraries like react-aria-modal that handle focus restoration and trap focus within the dialog[8].

When to Avoid Autofocus

  • Multi-step forms where instructions precede inputs[1]
  • Pages with critical content above interactive elements[10]
  • Dynamic content that loads asynchronously[6][10]

By carefully evaluating these trade-offs and testing with assistive technologies, developers can balance usability and accessibility while adhering to WCAG standards[1][8][10].

Citations:


Attention: Reevaluate AutoFocus for Accessibility

The current use of the autoFocus attribute, even with the in-code rationale to keep focus "locked in while we do search," can inadvertently disrupt the natural focus order for keyboard and assistive technology users. AutoFocus may cause screen readers to skip critical content and lead to unexpected context shifts, particularly on mobile devices. Instead of relying on the attribute, consider managing focus programmatically—with React’s useRef and useEffect—to ensure focus is set only when appropriate and in a controlled manner. This change would help maintain a logical tab order and improve overall accessibility.

  • Replace autoFocus: Remove the native autoFocus attribute.
  • Implement controlled focus: Use useRef combined with a conditional useEffect for dynamic focus management.
  • Test user experience: Verify that the updated focus handling works correctly with keyboard navigation and screen readers.
apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.test.tsx (1)

1-195: Well-structured test suite with comprehensive coverage.

The tests thoroughly cover all expected behaviors of the useSearchStrategy hook including immediate execution, debouncing, throttling, and state management. The test cases are well-isolated and properly handle mock timers.

apps/dashboard/components/logs/llm-search/components/search-actions.tsx (1)

1-50: LGTM - Clean component implementation with clear responsibilities.

The component properly separates concerns and handles conditional rendering based on search state.

apps/dashboard/components/logs/llm-search/index.tsx (3)

3-7: LGTM - Good organization of imports.

The imports are well-organized, making it clear what external dependencies and local components are being used.


44-45: LGTM - Good use of the custom hook.

The component properly integrates the useSearchStrategy hook, destructuring all the necessary functions.


81-93: LGTM - Clean switch statement with good handling of different search modes.

The switch statement clearly handles different search modes with appropriate actions for each case.

@ogzhanolguncu ogzhanolguncu requested a review from chronark March 3, 2025 11:40
Copy link
Contributor Author

@chronark You've been summoned to review this PR

Copy link
Collaborator

chronark commented Mar 3, 2025

still has the same issues as on friday

  • cards are small, why don’t we do w-1/3? anymore
  • title overflows and should be truncated
  • search blocks input while loading

Copy link
Collaborator

chronark commented Mar 3, 2025

Copy link
Contributor Author

I've actually add those :thinkies:

Copy link
Collaborator

chronark commented Mar 3, 2025

https://dashboard-git-apis-overview-v2-unkey.vercel.app/apis
maybe I’m on the wrong preview?

Copy link
Contributor Author

don't think so but there are no different versions of input. something aint right lemme check

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: 1

🧹 Nitpick comments (2)
apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (2)

23-23: Consider adding error handling for API fetch failures.

The hook extracts loading states but doesn't appear to handle error states. Adding error handling would improve user experience when API calls fail.

- const { total, loadMore, isLoading, hasMore } = useFetchApiOverview(initialData, setApiList);
+ const { total, loadMore, isLoading, hasMore, error } = useFetchApiOverview(initialData, setApiList);

Then add error state handling in the component, perhaps showing an error message when error is truthy.


50-64: Consider adding aria-label to the loading spinner for better accessibility.

The loading spinner lacks an accessible label, which might create issues for screen reader users.

- <div className="animate-spin h-4 w-4 border-2 border-gray-7 border-t-transparent rounded-full" />
+ <div 
+   className="animate-spin h-4 w-4 border-2 border-gray-7 border-t-transparent rounded-full" 
+   role="status"
+   aria-label="Loading more APIs"
+ />
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 88fbb72 and c3c36ae.

📒 Files selected for processing (1)
  • apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (18)
  • GitHub Check: Test Packages / Test ./packages/rbac
  • GitHub Check: Test Packages / Test ./packages/nextjs
  • GitHub Check: Test Packages / Test ./packages/hono
  • GitHub Check: Test Packages / Test ./packages/cache
  • GitHub Check: Test Packages / Test ./packages/api
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Packages / Test ./internal/resend
  • GitHub Check: Test Packages / Test ./internal/keys
  • GitHub Check: Test Packages / Test ./internal/id
  • GitHub Check: Test Packages / Test ./internal/hash
  • GitHub Check: Test Packages / Test ./internal/encryption
  • GitHub Check: Test Packages / Test ./internal/billing
  • GitHub Check: Test Go API Local / test_agent_local
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/dashboard/app/(app)/apis/_components/api-list-grid.tsx (6)

1-11: Imports look well-organized and appropriate.

All necessary dependencies are imported correctly, including React hooks, type definitions, and UI components.


12-22: Props interface is well-defined with appropriate typing.

The component accepts a clear set of props with proper TypeScript typing, making the component's API easy to understand and use.


25-37: Empty state is well-implemented with clear messaging.

The empty state provides good user feedback with appropriate messaging when no APIs are found.


46-49: Clear pagination status indicator.

Showing the number of displayed items versus total is good UX practice.


41-41:

Details

✅ Verification successful

Verify that the grid layout addresses the title overflow issue mentioned in PR feedback.

According to PR feedback, there were issues with titles overflowing. Ensure that the titles in ApiListCard are properly truncated, as this grid layout might not fully address that issue.


🏁 Script executed:

#!/bin/bash
# Check how titles are handled in the ApiListCard component
rg "title|overflow|truncate" apps/dashboard/app/\(app\)/apis/_components/api-list-card.tsx

Length of output: 153


Title Overflow Handling Verified

The grep results confirm that title truncation is appropriately handled within the ApiListCard component (using <div className="text-xs text-accent-9 truncate">). This means that the grid layout in api-list-grid.tsx does not need to address title overflow issues, as each card already manages it.


23-23:

Details

✅ Verification successful

Verify that search functionality doesn't block during loading.

PR feedback mentioned that "search is blocking input while loading." This component handles loading states for the "Load more" button, but we should ensure the search input (likely in a parent component) isn't blocked during API fetching operations.

Also applies to: 50-64


🏁 Script executed:

#!/bin/bash
# Look for any search input components that might be related
rg "search|input.*disabled|isSearching" apps/dashboard/app/\(app\)/apis/ --type tsx -A 5 -B 5

Length of output: 123


🏁 Script executed:

#!/bin/bash
# Re-run the search for potential search input components by adding a TSX file type definition.
rg --type-add 'tsx:*.tsx' --type tsx "search|input.*disabled|isSearching" apps/dashboard/app/\(app\)/apis/ -A 5 -B 5

Length of output: 20691


Action: Search input responsiveness verified during load operations
After reviewing the components, I found that the search functionality remains interactive during API loading. Specifically:

  • The search input (in the LogsSearch component) is configured with searchMode="allowTypeDuringSearch", which permits typing even while a search mutation is in progress.
  • The isSearching state properly controls the conditional rendering in the ApiList components (e.g., the "Load More" button is hidden when isSearching is true), ensuring that the loading state for fetching API data does not interfere with search input.

No further changes seem necessary based on the current implementation.

@chronark chronark added this pull request to the merge queue Mar 3, 2025
Merged via the queue into main with commit ad02627 Mar 3, 2025
30 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Apr 7, 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